diff --git a/repos/os/recipes/src/virtio_input_drv/content.mk b/repos/os/recipes/src/virtio_input_drv/content.mk
new file mode 100644
index 0000000000..af681f7e1f
--- /dev/null
+++ b/repos/os/recipes/src/virtio_input_drv/content.mk
@@ -0,0 +1,2 @@
+SRC_DIR = src/drivers/input/virtio
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
diff --git a/repos/os/recipes/src/virtio_input_drv/hash b/repos/os/recipes/src/virtio_input_drv/hash
new file mode 100644
index 0000000000..471dbb0a71
--- /dev/null
+++ b/repos/os/recipes/src/virtio_input_drv/hash
@@ -0,0 +1 @@
+2021-10-05 1388c0c89ec4e1cf3fecdd595eb8bf358c4310e8
diff --git a/repos/os/recipes/src/virtio_input_drv/used_apis b/repos/os/recipes/src/virtio_input_drv/used_apis
new file mode 100644
index 0000000000..95cde362d9
--- /dev/null
+++ b/repos/os/recipes/src/virtio_input_drv/used_apis
@@ -0,0 +1,5 @@
+base
+os
+virtio
+event_session
+platform_session
diff --git a/repos/os/src/drivers/input/virtio/README b/repos/os/src/drivers/input/virtio/README
new file mode 100644
index 0000000000..d7741f89da
--- /dev/null
+++ b/repos/os/src/drivers/input/virtio/README
@@ -0,0 +1,12 @@
+The VirtIO input driver makes it possible to read input events from
+Qemu keyboard, mouse, or tablet devices and feed them to Genode Event
+session.
+
+By default the driver can attach to any supported device type. This
+may be changed by specifying "match_product" configuration key. Allowed
+values for this setting are: keyboard, mouse, tablet, any (default).
+
+For example, to make sure specific instance of this driver attaches only
+to VirtIO keyboard device it can be configured as:
+
+!
diff --git a/repos/os/src/drivers/input/virtio/component.h b/repos/os/src/drivers/input/virtio/component.h
new file mode 100644
index 0000000000..c06512b373
--- /dev/null
+++ b/repos/os/src/drivers/input/virtio/component.h
@@ -0,0 +1,458 @@
+/*
+ * \brief VirtIO based input driver
+ * \author Piotr Tworek
+ * \date 2020-02-01
+ */
+
+/*
+ * Copyright (C) 2020 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.
+ */
+
+/* Genode */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+namespace Virtio_input {
+ using namespace Genode;
+ struct Main;
+ class Driver;
+}
+
+
+class Virtio_input::Driver
+{
+ public:
+
+ struct Device_not_found : Exception { };
+ struct Device_init_failed : Exception { };
+ struct Queue_init_failed : Exception { };
+
+ private:
+
+ enum { VENDOR_QEMU = 0x0627 };
+
+ enum Product {
+ Any = 0x0,
+ Keyboard = 0x1,
+ Mouse = 0x2,
+ Tablet = 0x3,
+ };
+
+ enum Config : uint8_t {
+ SelectID = 0x00,
+ SelectSubID = 0x01,
+ Data_size = 0x02,
+ Data = 0x08,
+ };
+
+ enum Config_id : uint8_t {
+ Name = 0x01,
+ Serial = 0x02,
+ Device_id = 0x03,
+ Prop_bits = 0x10,
+ Event_bits = 0x11,
+ Abs_info = 0x12,
+ };
+
+ struct Device_id {
+ uint16_t bus_type = 0;
+ uint16_t vendor = 0;
+ uint16_t product = 0;
+ uint16_t version = 0;
+ };
+
+ struct Features : Register<64> { struct VERSION_1 : Bitfield<32, 1> { }; };
+
+ enum Queue_id : uint16_t { EVENTS_VQ = 0, STATUS_VQ = 1 };
+
+ struct Abs_config {
+ struct {
+ uint32_t min;
+ uint32_t max;
+ } x, y;
+ uint32_t width;
+ uint32_t height;
+ };
+
+ struct Event {
+ enum Type {
+ Syn = 0x00,
+ Key = 0x01,
+ Rel = 0x02,
+ Abs = 0x03,
+ Rep = 0x14,
+ };
+ enum Code {
+ Rel_x = 0x00,
+ Rel_y = 0x01,
+ Rel_wheel = 0x08,
+ Abs_x = 0x00,
+ Abs_y = 0x01,
+ };
+ uint16_t type;
+ uint16_t code;
+ uint32_t value;
+
+ bool operator == (Event const &other) const {
+ return other.type == this->type &&
+ other.code == this->code &&
+ other.value == this->value;
+ }
+ };
+
+ struct Events_queue_traits {
+ static const bool device_write_only = true;
+ static const bool has_data_payload = false;
+ };
+
+ struct Status_queue_traits {
+ static const bool device_write_only = false;
+ static const bool has_data_payload = false;
+ };
+
+ enum { QUEUE_SIZE = 64, QUEUE_ELM_SIZE = sizeof(Event) };
+
+ typedef Virtio::Queue Events_virtqueue;
+ typedef Virtio::Queue Status_virtqueue;
+
+ Driver(Driver const &);
+ Driver &operator = (Driver const &);
+
+ Env &_env;
+ ::Event::Connection _event_session { _env };
+ Virtio::Device &_device;
+ Event _last_sent_key_event { 0, 0, 0 };
+ Input::Relative_motion _rel_motion { 0, 0 };
+ Input::Absolute_motion _abs_motion { -1, -1 };
+ Abs_config _abs_config { { 0, 0 }, { 0, 0 }, 0, 0 };
+ Signal_handler _irq_handler {_env.ep(), *this, &Driver::_handle_irq};
+ Events_virtqueue _events_vq { _env.ram(), _env.rm(),
+ QUEUE_SIZE, QUEUE_ELM_SIZE };
+ Status_virtqueue _status_vq { _env.ram(), _env.rm(),
+ QUEUE_SIZE, QUEUE_ELM_SIZE };
+
+
+ void _handle_event(::Event::Session_client::Batch &batch, const Event &evt)
+ {
+ switch (evt.type) {
+
+ case Event::Type::Syn:
+ {
+ if (_rel_motion.x != 0 || _rel_motion.y != 0) {
+ batch.submit(_rel_motion);
+ _rel_motion = Input::Relative_motion{0, 0};
+ }
+
+ if (_abs_motion.x >= 0 || _abs_motion.y >= 0) {
+ batch.submit(_abs_motion);
+ _abs_motion = Input::Absolute_motion{-1, -1};
+ }
+
+ break;
+ }
+
+ case Event::Type::Rel:
+ {
+ switch (evt.code) {
+ case Event::Code::Rel_x: _rel_motion.x = evt.value; break;
+ case Event::Code::Rel_y: _rel_motion.y = evt.value; break;
+ case Event::Code::Rel_wheel:
+ batch.submit(Input::Wheel{0, (int)evt.value});
+ break;
+ default:
+ warning("Unhandled relative event code: ", Hex(evt.code));
+ break;
+ }
+ break;
+ }
+
+ case Event::Type::Key:
+ {
+ /* filter out auto-repeat keypress events */
+ if (_last_sent_key_event == evt)
+ break;
+
+ /* Genode keyboard event codes mirror linux evdev ones */
+ Input::Keycode keycode = static_cast(evt.code);
+
+ /*
+ * Some key events apparently don't send both press and
+ * release values. Fake both press and release to make
+ * nitpicker happy.
+ */
+ if ((keycode == Input::BTN_GEAR_UP ||
+ keycode == Input::BTN_GEAR_DOWN) && !evt.value)
+ batch.submit(Input::Press{keycode});
+
+ switch (evt.value) {
+ case 0: batch.submit(Input::Release{keycode}); break;
+ case 1: batch.submit(Input::Press{keycode}); break;
+ default:
+ warning("Unhandled key event value: ", evt.value);
+ break;
+ }
+
+ _last_sent_key_event = evt;
+ break;
+ }
+
+ case Event::Type::Abs:
+ {
+ switch (evt.code) {
+ case Event::Code::Abs_x:
+ _abs_motion.x = (_abs_config.width * evt.value / _abs_config.x.max);
+ _abs_motion.y = max(0, _abs_motion.y);
+ break;
+ case Event::Code::Abs_y:
+ _abs_motion.x = max(0, _abs_motion.x);
+ _abs_motion.y = (_abs_config.height * evt.value / _abs_config.y.max);
+ break;
+ default:
+ warning("Unhandled absolute event code: ", Hex(evt.code));
+ break;
+ }
+ break;
+ }
+
+ default:
+ warning("Unhandled event type: ", Hex(evt.type));
+ break;
+ }
+ }
+
+
+ static Product _match_product(Xml_node const &config)
+ {
+ auto product_string = config.attribute_value("match_product", String<10>("any"));
+
+ if (product_string == "keyboard") {
+ return Product::Keyboard;
+ } else if (product_string == "mouse") {
+ return Product::Mouse;
+ } else if (product_string == "tablet") {
+ return Product::Tablet;
+ } else if (product_string == "any") {
+ return Product::Any;
+ }
+
+ error("Invalid product name: ", product_string);
+
+ throw Device_init_failed();
+ }
+
+
+ static size_t _cfg_select(Virtio::Device &device, Config_id sel, uint8_t subsel)
+ {
+ device.write_config(Config::SelectID, Virtio::Device::ACCESS_8BIT, sel);
+ device.write_config(Config::SelectSubID, Virtio::Device::ACCESS_8BIT, subsel);
+ return device.read_config(Config::Data_size, Virtio::Device::ACCESS_8BIT);
+ }
+
+
+ static Abs_config _read_abs_config(Virtio::Device &device,
+ Xml_node const &config)
+ {
+ Abs_config cfg { {0, ~0U}, {0, ~0U}, 0, 0};
+
+ auto size = _cfg_select(device, Config_id::Abs_info, Event::Code::Abs_x);
+ if (size >= sizeof(cfg.x)) {
+ cfg.x.min = device.read_config(Config::Data, Virtio::Device::ACCESS_32BIT);
+ cfg.x.max = device.read_config(Config::Data + 4, Virtio::Device::ACCESS_32BIT);
+ }
+
+ size = _cfg_select(device, Config_id::Abs_info, Event::Code::Abs_y);
+ if (size >= sizeof(cfg.y)) {
+ cfg.y.min = device.read_config(Config::Data, Virtio::Device::ACCESS_32BIT);
+ cfg.y.max = device.read_config(Config::Data + 4, Virtio::Device::ACCESS_32BIT);
+ }
+
+ cfg.width = config.attribute_value("width", cfg.x.max);
+ cfg.height = config.attribute_value("height", cfg.y.max);
+
+ return cfg;
+ }
+
+
+ static struct Device_id _read_device_id(Virtio::Device &device)
+ {
+ struct Device_id device_id;
+ auto size = _cfg_select(device, Config_id::Device_id, 0);
+
+ if (size != sizeof(device_id)) {
+ error("Invalid VirtIO input device ID size!");
+ throw Device_init_failed();
+ }
+
+ device_id.bus_type = device.read_config(Config::Data + 0, Virtio::Device::ACCESS_16BIT);
+ device_id.vendor = device.read_config(Config::Data + 2, Virtio::Device::ACCESS_16BIT);
+ device_id.product = device.read_config(Config::Data + 4, Virtio::Device::ACCESS_16BIT);
+ device_id.version = device.read_config(Config::Data + 6, Virtio::Device::ACCESS_16BIT);
+
+ return device_id;
+ }
+
+
+ template
+ static String _read_device_name(Virtio::Device &device)
+ {
+ auto size = _cfg_select(device, Config_id::Name, 0);
+ size = min(size, SZ);
+
+ char buf[SZ];
+ memset(buf, 0, sizeof(buf));
+
+ for (unsigned i = 0; i < size; ++i)
+ buf[i] = device.read_config(Config::Data + i, Virtio::Device::ACCESS_8BIT);
+
+ return String(buf);
+ }
+
+
+ static bool _probe_device(Virtio::Device &device, Product match_product)
+ {
+ using Status = Virtio::Device::Status;
+
+ if (!device.set_status(Status::RESET)) {
+ warning("Failed to reset the device!");
+ return false;
+ }
+
+ if (!device.set_status(Status::ACKNOWLEDGE)) {
+ warning("Failed to acknowledge the device!");
+ return false;
+ }
+
+ const auto dev_id = _read_device_id(device);
+ if (dev_id.vendor != VENDOR_QEMU) {
+ warning("Unsupported VirtIO input device vendor: ", Hex(dev_id.vendor));
+ }
+
+ if (match_product != Product::Any && match_product != dev_id.product)
+ return false;
+
+ return true;
+ }
+
+
+ static bool _init_features(Virtio::Device &device)
+ {
+ using Status = Virtio::Device::Status;
+
+ const uint32_t low = device.get_features(0);
+ const uint32_t high = device.get_features(1);
+ const Features::access_t device_features = ((uint64_t)high << 32) | low;
+
+ Features::access_t driver_features = 0;
+
+ if (!Features::VERSION_1::get(device_features)) {
+ warning("Unsupprted VirtIO device version!");
+ return false;
+ }
+ Features::VERSION_1::set(driver_features);
+
+ device.set_features(0, (uint32_t)driver_features);
+ device.set_features(1, (uint32_t)(driver_features >> 32));
+
+ if (!device.set_status(Status::FEATURES_OK)) {
+ device.set_status(Status::FAILED);
+ error("Device feature negotiation failed!");
+ return false;
+ }
+
+ return true;
+ }
+
+
+ void _init_driver(Xml_node const &config)
+ {
+ using Status = Virtio::Device::Status;
+
+ const auto product = _match_product(config);
+
+ if (_probe_device(_device, product) && _init_features(_device)) {
+
+ if (!_device.set_status(Status::DRIVER)) {
+ _device.set_status(Status::FAILED);
+ warning("Device initialization failed!");
+ throw Device_init_failed();
+ }
+
+ return;
+ } else if (!_device.set_status(Status::RESET)) {
+ warning("Failed to reset the device!");
+ }
+
+ warning("No suitable VirtIO input device found!");
+ throw Device_not_found();
+ }
+
+
+ void _setup_virtio_queues()
+ {
+ if (!_device.configure_queue(EVENTS_VQ, _events_vq.description())) {
+ error("Failed to initialize events VirtIO queue!");
+ throw Queue_init_failed();
+ }
+
+ if (!_device.configure_queue(STATUS_VQ, _status_vq.description())) {
+ error("Failed to initialize status VirtIO queue!");
+ throw Queue_init_failed();
+ }
+
+ using Status = Virtio::Device::Status;
+ if (!_device.set_status(Status::DRIVER_OK)) {
+ _device.set_status(Status::FAILED);
+ error("Failed to initialize VirtIO queues!");
+ throw Queue_init_failed();
+ }
+ }
+
+
+ void _handle_irq()
+ {
+ enum { IRQ_USED_RING_UPDATE = 1, IRQ_CONFIG_CHANGE = 2 };
+
+ const uint32_t reasons = _device.read_isr();
+
+ if (reasons & IRQ_USED_RING_UPDATE) {
+ _event_session.with_batch([&] (::Event::Session_client::Batch &batch) {
+ while (_events_vq.has_used_buffers())
+ _handle_event(batch, _events_vq.read_data());
+ });
+
+ /* Reclaim all buffers processed by the device. */
+ if (_status_vq.has_used_buffers())
+ _status_vq.ack_all_transfers();
+ }
+
+ _device.irq_ack();
+ }
+
+
+ public:
+
+ Driver(Env &env,
+ Virtio::Device &device,
+ Xml_node const &config)
+ :
+ _env(env), _device(device)
+ {
+ _init_driver(config);
+ _abs_config = _read_abs_config(_device, config);
+ _setup_virtio_queues();
+ _device.irq_sigh(_irq_handler);
+ _device.irq_ack();
+ log("Using \"", _read_device_name<32>(_device), "\" device.");
+ }
+
+};
diff --git a/repos/os/src/drivers/input/virtio/mmio_device.cc b/repos/os/src/drivers/input/virtio/mmio_device.cc
new file mode 100644
index 0000000000..641d98aef8
--- /dev/null
+++ b/repos/os/src/drivers/input/virtio/mmio_device.cc
@@ -0,0 +1,44 @@
+/*
+ * \brief VirtIO MMIO input driver
+ * \author Piotr Tworek
+ * \date 2020-02-14
+ */
+
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include
+#include
+#include
+
+#include "component.h"
+
+namespace Virtio_mmio_input {
+ using namespace Genode;
+ struct Main;
+}
+
+
+struct Virtio_mmio_input::Main
+{
+ Env &env;
+ Platform::Connection platform { env };
+ Platform::Device platform_device { platform, { "input" } };
+ Virtio::Device virtio_device { platform_device };
+ Attached_rom_dataspace config { env, "config" };
+ Virtio_input::Driver driver { env, virtio_device, config.xml() };
+
+ Main(Env &env)
+ try : env(env) { log("--- VirtIO MMIO input driver started ---"); }
+ catch (...) { env.parent().exit(-1); }
+};
+
+
+void Component::construct(Genode::Env &env)
+{
+ static Virtio_mmio_input::Main main(env);
+}
diff --git a/repos/os/src/drivers/input/virtio/spec/arm/target.mk b/repos/os/src/drivers/input/virtio/spec/arm/target.mk
new file mode 100644
index 0000000000..89090dc1c3
--- /dev/null
+++ b/repos/os/src/drivers/input/virtio/spec/arm/target.mk
@@ -0,0 +1,3 @@
+REQUIRES = arm
+
+include $(REP_DIR)/src/drivers/input/virtio/target_mmio.inc
diff --git a/repos/os/src/drivers/input/virtio/spec/arm_64/target.mk b/repos/os/src/drivers/input/virtio/spec/arm_64/target.mk
new file mode 100644
index 0000000000..7443d832aa
--- /dev/null
+++ b/repos/os/src/drivers/input/virtio/spec/arm_64/target.mk
@@ -0,0 +1,3 @@
+REQUIRES = arm_64
+
+include $(REP_DIR)/src/drivers/input/virtio/target_mmio.inc
diff --git a/repos/os/src/drivers/input/virtio/target_mmio.inc b/repos/os/src/drivers/input/virtio/target_mmio.inc
new file mode 100644
index 0000000000..19c0408141
--- /dev/null
+++ b/repos/os/src/drivers/input/virtio/target_mmio.inc
@@ -0,0 +1,6 @@
+TARGET = virtio_mmio_input_drv
+SRC_CC = mmio_device.cc
+LIBS = base
+INC_DIR = $(REP_DIR)/src/drivers/input/virtio
+
+vpath % $(REP_DIR)/src/drivers/input/virtio