diff --git a/repos/os/recipes/src/virtio_fb_drv/content.mk b/repos/os/recipes/src/virtio_fb_drv/content.mk
new file mode 100644
index 0000000000..44917f3a41
--- /dev/null
+++ b/repos/os/recipes/src/virtio_fb_drv/content.mk
@@ -0,0 +1,2 @@
+SRC_DIR = src/drivers/framebuffer/virtio
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
diff --git a/repos/os/recipes/src/virtio_fb_drv/hash b/repos/os/recipes/src/virtio_fb_drv/hash
new file mode 100644
index 0000000000..e236df5623
--- /dev/null
+++ b/repos/os/recipes/src/virtio_fb_drv/hash
@@ -0,0 +1 @@
+2021-09-21 505752c4facfcd13ceb75f90b7c9f43209586513
diff --git a/repos/os/recipes/src/virtio_fb_drv/used_apis b/repos/os/recipes/src/virtio_fb_drv/used_apis
new file mode 100644
index 0000000000..51a066a8c4
--- /dev/null
+++ b/repos/os/recipes/src/virtio_fb_drv/used_apis
@@ -0,0 +1,7 @@
+base
+blit
+capture_session
+event_session
+os
+platform_session
+virtio
diff --git a/repos/os/src/drivers/framebuffer/virtio/component.h b/repos/os/src/drivers/framebuffer/virtio/component.h
new file mode 100644
index 0000000000..d36f7bd803
--- /dev/null
+++ b/repos/os/src/drivers/framebuffer/virtio/component.h
@@ -0,0 +1,563 @@
+/*
+ * \brief VirtIO MMIO Framebuffer component
+ * \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
+#include
+#include
+#include
+
+namespace Virtio_fb {
+ using namespace Genode;
+ class Driver;
+}
+
+/*
+ * This driver is based on Virtual I/O Device specification, Version 1.1, chapter 5.7
+ * "GPU Device". This document can be found at:
+ * https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html
+ */
+class Virtio_fb::Driver
+{
+ private:
+
+ /*
+ * Noncopyable
+ */
+ Driver(Root const &) = delete;
+ Driver &operator = (Driver const &) = delete;
+
+ struct Device_init_failed : Exception { };
+ struct Unsupported_version : Exception { };
+ struct Features_init_failed : Exception { };
+ struct Queue_init_failed : Exception { };
+ struct Display_init_failed : Exception { };
+ struct Display_deinit_failed : Exception { };
+
+ enum : uint16_t { CONTROL_VQ = 0, CURSOR_VQ = 1 };
+
+ struct Features : Register<64>
+ {
+ struct VIRGL : Bitfield<0, 1> { };
+ struct EDID : Bitfield<1, 1> { };
+ struct VERSION_1 : Bitfield<32, 1> { };
+ };
+
+ struct Control_header
+ {
+ enum Type : uint32_t
+ {
+ /* 2D commands */
+ CMD_GET_DISPLAY_INFO = 0x0100,
+ CMD_RESOURCE_CREATE_2D,
+ CMD_RESOURCE_UNREF,
+ CMD_RESOURCE_SET_SCANOUT,
+ CMD_RESOURCE_FLUSH,
+ CMD_RESOURCE_TRANSFER_TO_HOST,
+ CMD_RESOURCE_ATTACH_BACKING,
+ CMD_RESOURCE_DETACH_BACKING,
+
+ /* Success responses */
+ RESP_OK_NODATA = 0x1100,
+ RESP_OK_DISPLAY_INFO,
+ RESP_OK_CAPSET_INFO,
+ RESP_OK_CAPSET,
+ RESP_OK_EDID,
+
+ /* Error responses */
+ RESP_ERROR_UNSPECIFIED = 0x1200,
+ RESP_ERROR_OUT_OF_MEMORY,
+ RESP_ERROR_INVALID_SCANOUT_ID,
+ RESP_ERROR_INVALID_RESOURCE_ID,
+ RESP_ERROR_INVALID_CONTEXT_ID,
+ RESP_ERROR_INVALID_PARAMETER_ID,
+ };
+
+ Type type;
+ uint32_t flags = 0;
+ uint64_t fence_id = 0;
+ uint32_t ctx_id = 0;
+ uint32_t const padding = 0;
+ };
+
+ enum { MAX_SCANOUTS = 16 };
+ enum : uint32_t { EVENT_DISPLAY = (1 << 0) };
+
+ enum Config : uint8_t
+ {
+ EVENTS_READ = 0,
+ EVENTS_CLEAR = 4,
+ NUM_SCANOUTS = 8,
+ };
+
+ struct Rect
+ {
+ uint32_t x;
+ uint32_t y;
+ uint32_t width;
+ uint32_t height;
+ };
+
+ struct Display_info
+ {
+ Control_header hdr;
+ struct
+ {
+ Rect rect;
+ uint32_t enabled;
+ uint32_t flags;
+ } modes[MAX_SCANOUTS];
+ };
+
+ enum class Resource_Id : uint32_t { FRAMEBUFFER = 1 };
+
+ enum class Format : uint32_t { B8G8R8X8 = 2 };
+
+ struct Resource_create_2d
+ {
+ Resource_Id const resource_id = Resource_Id::FRAMEBUFFER;
+ Format const format = Format::B8G8R8X8;
+ uint32_t width;
+ uint32_t height;
+ };
+
+ struct Resource_destroy_2d
+ {
+ Resource_Id const resource_id = Resource_Id::FRAMEBUFFER;
+ uint32_t const padding = 0;
+ };
+
+ struct Attach_backing
+ {
+ Resource_Id const resource_id = Resource_Id::FRAMEBUFFER;
+ uint32_t const nr_entries = 1;
+ uint64_t addr;
+ uint32_t length;
+ uint32_t const padding = 0;
+ };
+
+ using Detach_backing = Resource_destroy_2d;
+
+ struct Set_scanout
+ {
+ Rect rect;
+ uint32_t scanout_id;
+ Resource_Id const resource_id = Resource_Id::FRAMEBUFFER;
+ };
+
+ struct Transfer_to_host_2d
+ {
+ Rect rect;
+ uint64_t offset;
+ Resource_Id const resource_id = Resource_Id::FRAMEBUFFER;
+ uint32_t const paddiing = 0;
+ };
+
+ struct Resource_flush
+ {
+ Rect rect;
+ Resource_Id const resource_id = Resource_Id::FRAMEBUFFER;
+ uint32_t const padding = 0;
+ };
+
+ struct Control_queue_traits
+ {
+ static const bool device_write_only = false;
+ static const bool has_data_payload = true;
+ };
+
+ typedef Virtio::Queue Control_queue;
+
+ class Fb_memory_resource
+ {
+ private:
+
+ /*
+ * Noncopyable
+ */
+ Fb_memory_resource(Fb_memory_resource const &) = delete;
+ Fb_memory_resource &operator = (Fb_memory_resource const &) = delete;
+
+ Platform::Connection &_platform;
+ Attached_dataspace _attachement;
+
+ static size_t _fb_size(Capture::Area const &area) {
+ return area.w() * area.h() * 4; }
+
+ public:
+
+ addr_t phys_addr()
+ {
+ Dataspace_client client(_attachement.cap());
+ return client.phys_addr();
+ }
+
+ Capture::Pixel *local_addr() { return _attachement.local_addr(); }
+ size_t size() const { return _attachement.size(); };
+
+ Fb_memory_resource(Region_map &rm,
+ Platform::Connection &platform,
+ Capture::Area const &area)
+ : _platform(platform)
+ , _attachement(rm, _platform.alloc_dma_buffer(_fb_size(area), UNCACHED)) { }
+
+ ~Fb_memory_resource()
+ {
+ _platform.free_dma_buffer(static_cap_cast(_attachement.cap()));
+ _attachement.invalidate();
+ }
+ };
+
+ Env &_env;
+ Platform::Connection &_platform;
+ Virtio::Device &_device;
+ Control_queue _ctrl_vq { _env.ram(), _env.rm(), 4, 512 };
+ uint32_t const _num_scanouts;
+ Signal_handler _irq_handler { _env.ep(), *this, &Driver::_handle_irq };
+
+ /*
+ * Capture
+ */
+ uint32_t _selected_scanout_id { 0 };
+ Capture::Area _display_area { 0, 0 };
+ Capture::Connection _capture { _env };
+ Constructible _captured_screen { };
+ Timer::Connection _capture_timer { _env };
+ Constructible _fb_res { };
+
+ Signal_handler _capture_timer_handler {
+ _env.ep(), *this, &Driver::_handle_capture_timer };
+
+
+ static uint32_t _read_num_scanouts(Virtio::Device &device)
+ {
+ uint32_t num_scanouts;
+ uint32_t before = 0, after = 0;
+ do {
+ num_scanouts = device.read_config(
+ Config::NUM_SCANOUTS, Virtio::Device::ACCESS_32BIT);
+ } while (after != before);
+ return num_scanouts;
+ }
+
+ uint32_t _read_pending_events()
+ {
+ uint32_t events;
+ uint32_t before = 0, after = 0;
+ do {
+ events = _device.read_config(
+ Config::EVENTS_READ, Virtio::Device::ACCESS_32BIT);
+ } while (after != before);
+ do {
+ _device.write_config(
+ Config::EVENTS_CLEAR, Virtio::Device::ACCESS_32BIT, events);
+ } while (after != before);
+ return events;
+ }
+
+ static uint32_t _init_device(Virtio::Device &device,
+ Virtio::Queue_description const &ctrl_vq_desc)
+ {
+ using Status = Virtio::Device::Status;
+
+ if (!device.set_status(Status::RESET)) {
+ error("Failed to reset the device!");
+ throw Device_init_failed();
+ }
+
+ if (!device.set_status(Status::ACKNOWLEDGE)) {
+ error("Failed to acknowledge the device!");
+ throw Device_init_failed();
+ }
+
+ if (!device.set_status(Status::DRIVER)) {
+ device.set_status(Status::FAILED);
+ error("Device initialization failed!");
+ throw Device_init_failed();
+ }
+
+ 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;
+
+ /* This driver does not support legacy VirtIO versions. */
+ if (!Features::VERSION_1::get(device_features)) {
+ error("Unsupprted VirtIO device version!");
+ throw Unsupported_version();
+ }
+ 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!");
+ throw Features_init_failed();
+ }
+
+ if (!device.configure_queue(CONTROL_VQ, ctrl_vq_desc)) {
+ error("Failed to initialize \"control\" 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();
+ }
+
+ auto num_scanouts = _read_num_scanouts(device);
+ if (num_scanouts > MAX_SCANOUTS) {
+ error("Invalid scanout number!");
+ throw Device_init_failed();
+ }
+
+ return num_scanouts;
+ }
+
+ void _configure_display() {
+ Control_header res2d_cmd { Control_header::CMD_RESOURCE_CREATE_2D };
+ Resource_create_2d res2d_data {
+ .width = _display_area.w(),
+ .height = _display_area.h(),
+ };
+
+ if (!_exec_cmd(res2d_cmd, res2d_data)) {
+ error("Failed to create 2D resource!");
+ throw Display_init_failed();
+ }
+
+ try {
+ _fb_res.construct(_env.rm(), _platform, _display_area);
+ } catch (...) {
+ error("Failed to allocate framebuffer!");
+ throw;
+ }
+
+ {
+ Control_header attach_cmd { Control_header::CMD_RESOURCE_ATTACH_BACKING };
+ Attach_backing attach_data {
+ .addr = _fb_res->phys_addr(),
+ .length = static_cast(_fb_res->size())
+ };
+
+ if (!_exec_cmd(attach_cmd, attach_data)) {
+ error("Failed to attach framebuffer backing!");
+ throw Display_init_failed();
+ }
+ }
+
+ Control_header scanout_cmd { Control_header::CMD_RESOURCE_SET_SCANOUT };
+ Set_scanout scanout_data {
+ .rect = {
+ .x = 0,
+ .y = 0,
+ .width = _display_area.w(),
+ .height = _display_area.h(),
+ },
+ .scanout_id = _selected_scanout_id,
+ };
+
+ if (!_exec_cmd(scanout_cmd, scanout_data)) {
+ error("Failed to assign framebuffer to a scanout!");
+ throw Display_init_failed();
+ }
+
+ _captured_screen.construct(_capture, _env.rm(), _display_area);
+ }
+
+ void _shutdown_display()
+ {
+ Control_header detach_cmd { Control_header::CMD_RESOURCE_DETACH_BACKING };
+ Detach_backing detach_data;
+
+ if (!_exec_cmd(detach_cmd, detach_data)) {
+ error("Failed to detatch framebuffer backing!");
+ throw Display_deinit_failed();
+ }
+
+ Control_header unref_cmd { Control_header::CMD_RESOURCE_UNREF };
+ Resource_destroy_2d unref_data;
+
+ if (!_exec_cmd(unref_cmd, unref_data)) {
+ error("Failed to unref framebuffer resource!");
+ throw Display_deinit_failed();
+ }
+
+ _captured_screen.destruct();
+ _fb_res.destruct();
+ }
+
+ void _reconfigure_display()
+ {
+ if (!_update_display_info(true)) {
+ error("Failed to update display info!");
+ throw Display_deinit_failed();
+ }
+
+ _shutdown_display();
+ _configure_display();
+ }
+
+ void _handle_irq()
+ {
+ const uint32_t reasons = _device.read_isr();
+
+ enum { IRQ_USED_RING_UPDATE = 1, IRQ_CONFIG_CHANGE = 2 };
+
+ /*
+ * This driver does not request interrupts when dealing with control
+ * queue. Some older pre 6.x Qemu versions do signal ctrl ring update
+ * when display size is changed. Just ACK and otherwise ignore such
+ * bogus updates.
+ */
+ if ((reasons & IRQ_USED_RING_UPDATE) && _ctrl_vq.has_used_buffers())
+ _ctrl_vq.ack_all_transfers();
+
+ if (reasons & IRQ_CONFIG_CHANGE) {
+ auto const events = _read_pending_events();
+ if (events & EVENT_DISPLAY)
+ _reconfigure_display();
+ }
+
+ _device.irq_ack();
+ }
+
+ void _handle_capture_timer()
+ {
+ using Pixel = Capture::Pixel;
+
+ if (!_captured_screen.constructed())
+ return;
+
+ Capture::Surface surface(_fb_res->local_addr(), _display_area);
+ _captured_screen->apply_to_surface(surface);
+
+ _update_fb();
+ }
+
+ void _flush_ctrl_vq()
+ {
+ _device.notify_buffers_available(CONTROL_VQ);
+ while (!_ctrl_vq.has_used_buffers());
+ }
+
+ template
+ bool _exec_cmd(Control_header const &cmd,
+ CMD_DATA_TYPE const &cmd_data)
+ {
+ return _ctrl_vq.write_data_read_reply(
+ cmd, (char const *)&cmd_data, sizeof(CMD_DATA_TYPE),
+ [this]() { _flush_ctrl_vq(); },
+ [](Control_header const &response) {
+ return response.type == Control_header::RESP_OK_NODATA; });
+ }
+
+ bool _update_display_info(bool use_current_scanout)
+ {
+ Control_header cmd { Control_header::CMD_GET_DISPLAY_INFO };
+
+ auto display_info_cb = [&](Display_info const &info) {
+ for (size_t i = 0; i < _num_scanouts; ++i) {
+ if (info.modes[i].enabled) {
+ if (use_current_scanout && (_selected_scanout_id != i))
+ continue;
+ auto const &r = info.modes[i].rect;
+ _display_area = Capture::Area{ r.width, r.height };
+ _selected_scanout_id = i;
+ return true;
+ }
+ }
+ return false;
+ };
+
+ if (!_ctrl_vq.write_data_read_reply(
+ cmd, [this] { _flush_ctrl_vq(); }, display_info_cb)) {
+ error("Failed to request display info!");
+ return false;
+ }
+
+ return true;
+ }
+
+ void _init_display() {
+ if (!_update_display_info(false))
+ throw Display_init_failed();
+ _configure_display();
+ }
+
+ void _update_fb()
+ {
+ Control_header transfer_cmd { Control_header::CMD_RESOURCE_TRANSFER_TO_HOST };
+ Transfer_to_host_2d transfer_data {
+ .rect = {
+ .x = 0,
+ .y = 0,
+ .width = _display_area.w(),
+ .height = _display_area.h(),
+ },
+ .offset = 0,
+ };
+ if (!_exec_cmd(transfer_cmd, transfer_data)) {
+ error("Failed to send transfer 2D resource to host command!");
+ return;
+ }
+
+ Control_header flush_cmd { Control_header::CMD_RESOURCE_FLUSH };
+ Resource_flush flush_data {
+ .rect = {
+ .x = 0,
+ .y = 0,
+ .width = _display_area.w(),
+ .height = _display_area.h(),
+ },
+ };
+ if (!_exec_cmd(flush_cmd, flush_data)) {
+ error("Failed to send flush resource command!");
+ return;
+ }
+ }
+
+ public:
+
+ Driver(Env &env,
+ Platform::Connection &platform,
+ Virtio::Device &device)
+ : _env(env),
+ _platform(platform),
+ _device(device),
+ _num_scanouts(_init_device(_device, _ctrl_vq.description()))
+ {
+ try {
+ _init_display();
+ } catch (...) {
+ device.set_status(Virtio::Device::Status::RESET);
+ throw;
+ }
+
+ _device.irq_sigh(_irq_handler);
+ _device.irq_ack();
+ _capture_timer.sigh(_capture_timer_handler);
+ _capture_timer.trigger_periodic(10*1000);
+ }
+
+ ~Driver()
+ {
+ _device.set_status(Virtio::Device::Status::RESET);
+ }
+};
diff --git a/repos/os/src/drivers/framebuffer/virtio/mmio_device.cc b/repos/os/src/drivers/framebuffer/virtio/mmio_device.cc
new file mode 100644
index 0000000000..0852d0635f
--- /dev/null
+++ b/repos/os/src/drivers/framebuffer/virtio/mmio_device.cc
@@ -0,0 +1,42 @@
+/*
+ * \brief VirtIO MMIO Framebuffer 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_fb {
+ using namespace Genode;
+ struct Main;
+}
+
+struct Virtio_mmio_fb::Main
+{
+ Genode::Env &env;
+ Platform::Connection platform { env };
+ Platform::Device platform_device { platform,
+ Platform::Device::Type { "gpu" } };
+ Virtio::Device virtio_device { platform_device };
+ Virtio_fb::Driver driver { env, platform, virtio_device };
+
+ Main(Env &env)
+ try : env(env) { log("--- VirtIO MMIO Framebuffer driver started ---"); }
+ catch (...) { env.parent().exit(-1); }
+};
+
+void Component::construct(Genode::Env &env)
+{
+ static Virtio_mmio_fb::Main main(env);
+}
diff --git a/repos/os/src/drivers/framebuffer/virtio/spec/arm/target.mk b/repos/os/src/drivers/framebuffer/virtio/spec/arm/target.mk
new file mode 100644
index 0000000000..deb31aef49
--- /dev/null
+++ b/repos/os/src/drivers/framebuffer/virtio/spec/arm/target.mk
@@ -0,0 +1,3 @@
+REQUIRES = arm
+
+include $(REP_DIR)/src/drivers/framebuffer/virtio/target_mmio.inc
diff --git a/repos/os/src/drivers/framebuffer/virtio/spec/arm_64/target.mk b/repos/os/src/drivers/framebuffer/virtio/spec/arm_64/target.mk
new file mode 100644
index 0000000000..488c889735
--- /dev/null
+++ b/repos/os/src/drivers/framebuffer/virtio/spec/arm_64/target.mk
@@ -0,0 +1,3 @@
+REQUIRES = arm_64
+
+include $(REP_DIR)/src/drivers/framebuffer/virtio/target_mmio.inc
diff --git a/repos/os/src/drivers/framebuffer/virtio/target_mmio.inc b/repos/os/src/drivers/framebuffer/virtio/target_mmio.inc
new file mode 100644
index 0000000000..36c892b677
--- /dev/null
+++ b/repos/os/src/drivers/framebuffer/virtio/target_mmio.inc
@@ -0,0 +1,6 @@
+TARGET = virtio_mmio_fb_drv
+SRC_CC = mmio_device.cc
+LIBS = base blit
+INC_DIR = $(REP_DIR)/src/drivers/framebuffer/virtio
+
+vpath % $(REP_DIR)/src/drivers/framebuffer/virtio