diff --git a/repos/os/recipes/src/virtdev_rom/content.mk b/repos/os/recipes/src/virtdev_rom/content.mk
new file mode 100644
index 0000000000..9307f711ae
--- /dev/null
+++ b/repos/os/recipes/src/virtdev_rom/content.mk
@@ -0,0 +1,2 @@
+SRC_DIR = src/drivers/virtdev_rom
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
diff --git a/repos/os/recipes/src/virtdev_rom/hash b/repos/os/recipes/src/virtdev_rom/hash
new file mode 100644
index 0000000000..d0356ead5d
--- /dev/null
+++ b/repos/os/recipes/src/virtdev_rom/hash
@@ -0,0 +1 @@
+2020-07-03 e1c0e261929b71468814fdbcba02c7e0f6455c25
diff --git a/repos/os/recipes/src/virtdev_rom/used_apis b/repos/os/recipes/src/virtdev_rom/used_apis
new file mode 100644
index 0000000000..ec3bf565df
--- /dev/null
+++ b/repos/os/recipes/src/virtdev_rom/used_apis
@@ -0,0 +1,2 @@
+base
+os
diff --git a/repos/os/src/drivers/virtdev_rom/README b/repos/os/src/drivers/virtdev_rom/README
new file mode 100644
index 0000000000..5daf22ecc8
--- /dev/null
+++ b/repos/os/src/drivers/virtdev_rom/README
@@ -0,0 +1,31 @@
+This driver probes available VirtIO MMIO devices on Qemu's virt platform and
+exports the results in XML format via a ROM session. The exported ROM module
+is suitable as configuration for the generic ARM platform driver.
+
+This driver can currently recognize NIC, block, console, RNG, GPU, and
+input devices. When a device of a given type is detected, it is assigned a
+name based on its type and index on a bus. For example, if the system has
+two VirtIO NIC devices, their names will be "nic0" and "nic1".
+
+To simplify probing devices of the same type, each XML device node has
+an additional "type" property node. The value of this property is simply
+set to a string indicating device type, without any extra indices, e.g.,
+"nic", "gpu", "input", etc.
+
+!
+!
+!
+!
+
+Configuration
+~~~~~~~~~~~~~
+
+The virtdev_rom component appends the unmodified content of its own configuration
+ROM to its output. This makes it possible to easily append extra policies
+to the device configuration produced by the driver, e.g.,:
+
+!
+!
+!
+!
+!
diff --git a/repos/os/src/drivers/virtdev_rom/main.cc b/repos/os/src/drivers/virtdev_rom/main.cc
new file mode 100644
index 0000000000..b7dc07ebe6
--- /dev/null
+++ b/repos/os/src/drivers/virtdev_rom/main.cc
@@ -0,0 +1,200 @@
+/*
+ * \brief Virt Qemu device config generator for ARM platform driver
+ * \author Piotr Tworek
+ * \date 2020-07-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.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Virtdev_rom {
+ using namespace Genode;
+ class Session_component;
+ class Root;
+ struct Main;
+};
+
+
+class Virtdev_rom::Session_component : public Rpc_object
+{
+ private:
+
+ /*
+ * Noncopyable
+ */
+ Session_component(Session_component const &) = delete;
+ Session_component &operator = (Session_component const &) = delete;
+
+ Rom_dataspace_capability const _rom_cap;
+
+ public:
+
+ Session_component(Rom_dataspace_capability cap) : _rom_cap(cap)
+ { }
+
+ Rom_dataspace_capability dataspace() override { return _rom_cap; }
+
+ void sigh(Signal_context_capability) override { }
+};
+
+
+class Virtdev_rom::Root : public Root_component
+{
+ private:
+
+ /*
+ * Noncopyable
+ */
+ Root(Root const &) = delete;
+ Root &operator = (Root const &) = delete;
+
+ Env &_env;
+ Ram_dataspace_capability _ds;
+
+ Session_component *_create_session(const char *) override
+ {
+ auto cap = static_cap_cast(_ds);
+ return new (md_alloc()) Session_component(
+ static_cap_cast(cap));
+ }
+
+ public:
+
+ Root(Env &env, Allocator &md_alloc, Ram_dataspace_capability &cap)
+ : Root_component(env.ep(), md_alloc),
+ _env(env),
+ _ds(cap)
+ { }
+};
+
+
+struct Virtdev_rom::Main
+{
+ enum {
+ /* Taken from include/hw/arm/virt.h in Qemu source tree. */
+ NUM_VIRTIO_TRANSPORTS = 32,
+ /* Taken from hw/arm/virt.c in Qemu source tree. */
+ BASE_ADDRESS = 0x0A000000,
+ DEVICE_SIZE = 0x200,
+ IRQ_BASE = 48,
+ VIRTIO_MMIO_MAGIC = 0x74726976,
+ };
+
+ enum { MAX_ROM_SIZE = 4096, DEVICE_NAME_LEN = 64 };
+
+ Env &_env;
+ Ram_dataspace_capability _ds { _env.ram().alloc(MAX_ROM_SIZE) };
+ Sliced_heap _heap { _env.ram(), _env.rm() };
+ Virtdev_rom::Root _root { _env, _heap, _ds };
+
+ struct Device : public Attached_mmio
+ {
+ struct Magic : Register<0x000, 32> { };
+ struct Id : Register<0x008, 32> {
+ enum Value {
+ INVALID = 0,
+ NIC = 1,
+ BLOCK = 2,
+ CONSOLE = 3,
+ RNG = 4,
+ GPU = 16,
+ INPUT = 18,
+ MAX_VAL = INPUT,
+ };
+ };
+
+ Device(Env &env, addr_t base, size_t size)
+ : Attached_mmio(env, base, size, false) { }
+ };
+
+ static char const *_name_for_id(unsigned id)
+ {
+ switch (id) {
+ case Device::Id::NIC : return "nic";
+ case Device::Id::BLOCK : return "block";
+ case Device::Id::CONSOLE : return "console";
+ case Device::Id::RNG : return "rng";
+ case Device::Id::GPU : return "gpu";
+ case Device::Id::INPUT : return "input";
+ default: {
+ warning("Unhandled VirtIO device ID: ", Hex(id));
+ return "virtio";
+ }
+ }
+ }
+
+ void _probe_devices()
+ {
+ Attached_dataspace ds(_env.rm(), _ds);
+ Attached_rom_dataspace config { _env, "config" };
+
+ Xml_generator xml(ds.local_addr(), ds.size(), "config", [&] ()
+ {
+ uint8_t device_type_idx[Device::Id::MAX_VAL] = { 0 };
+
+ for (size_t idx = 0; idx < NUM_VIRTIO_TRANSPORTS; ++idx) {
+ addr_t addr = BASE_ADDRESS + idx * DEVICE_SIZE;
+ Device device { _env, BASE_ADDRESS + idx * DEVICE_SIZE, DEVICE_SIZE };
+
+ if (device.read() != VIRTIO_MMIO_MAGIC) {
+ warning("Found non VirrtIO MMIO device @ ", addr);
+ continue;
+ }
+
+ auto id = device.read();
+ if (id == Device::Id::INVALID)
+ continue;
+
+ xml.node("device", [&] ()
+ {
+ static char name[DEVICE_NAME_LEN];
+ snprintf(name, sizeof(name), "%s%u", _name_for_id(id), device_type_idx[id - 1]++);
+ xml.attribute("name", name);
+ xml.node("io_mem", [&] () {
+ xml.attribute("address", addr);
+ xml.attribute("size", DEVICE_SIZE);
+ });
+ xml.node("irq", [&] () {
+ xml.attribute("number", IRQ_BASE + idx);
+ });
+ xml.node("property", [&] () {
+ xml.attribute("name", "type");
+ xml.attribute("value", _name_for_id(id));
+ });
+ });
+ }
+
+ config.xml().with_raw_content([&] (char const *txt, size_t sz) {
+ xml.append(txt, sz);
+ });
+ });
+ }
+
+ Main(Env &env) : _env(env)
+ {
+ _probe_devices();
+ env.parent().announce(_env.ep().manage(_root));
+ }
+
+ ~Main() { _env.ram().free(_ds); }
+};
+
+
+void Component::construct(Genode::Env &env)
+{
+ static Virtdev_rom::Main main(env);
+}
diff --git a/repos/os/src/drivers/virtdev_rom/target.mk b/repos/os/src/drivers/virtdev_rom/target.mk
new file mode 100644
index 0000000000..b4a142362a
--- /dev/null
+++ b/repos/os/src/drivers/virtdev_rom/target.mk
@@ -0,0 +1,3 @@
+TARGET = virtdev_rom
+SRC_CC = main.cc
+LIBS = base