vmm: add virtio block device model

* Add new virtio device model
* Extend test run-script with vfat block test image
* Add vmm depot src recipe
* Use packages in test run-script

Fix #4025
This commit is contained in:
Stefan Kalkowski 2021-02-19 11:02:58 +01:00 committed by Norman Feske
parent 2879aa003b
commit 90d9470dfd
11 changed files with 361 additions and 23 deletions

View File

@ -0,0 +1,2 @@
SRC_DIR = src/server/vmm
include $(GENODE_DIR)/repos/base/recipes/src/content.inc

View File

@ -0,0 +1 @@
2021-02-19 d3b2bfd047b47215092d5fad951ab172b3fcb468

View File

@ -0,0 +1,7 @@
base-hw
base
block_session
nic_session
os
terminal_session
timer_session

View File

@ -12,16 +12,18 @@ if { ![have_board imx7d_sabre] && ![have_board imx8q_evk] &&
exit 0
}
set build_components {
core init timer
server/terminal_crosslink
test/terminal_expect_send
server/log_terminal
server/nic_router
server/vmm
}
build $build_components
create_boot_directory
import_from_depot [depot_user]/src/[base_src] \
[depot_user]/src/init \
[depot_user]/src/libc \
[depot_user]/src/log_terminal \
[depot_user]/src/nic_router \
[depot_user]/src/terminal_crosslink \
[depot_user]/src/vfs \
[depot_user]/src/vfs_block \
[depot_user]/src/vmm
build { test/terminal_expect_send }
install_config {
<config verbose="yes" prio_levels="2">
@ -39,10 +41,12 @@ install_config {
<any-service><parent/><any-child/></any-service>
</default-route>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="nic_drv">
<binary name="nic_router" />
<resource name="RAM" quantum="8M"/>
@ -57,14 +61,29 @@ install_config {
</domain>
</config>
</start>
<start name="vfs_block">
<resource name="RAM" quantum="2M"/>
<provides> <service name="Block"/> </provides>
<config>
<vfs> <rom name="block.img"/> </vfs>
<default-policy file="/block.img" block_size="512"/>
</config>
<route>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="log_terminal">
<resource name="RAM" quantum="2M"/>
<provides> <service name="Terminal"/> </provides>
</start>
<start name="terminal_crosslink">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Terminal"/> </provides>
</start>
<start name="vmm" caps="200" priority="-1">
<resource name="RAM" quantum="256M"/>
<route>
@ -74,10 +93,11 @@ install_config {
<any-service><parent/><any-child/></any-service>
</route>
</start>
<start name="vm">
<binary name="test-terminal_expect_send"/>
<resource name="RAM" quantum="1M"/>
<config expect="/ #" send="ls" verbose="yes"/>
<config expect="/ #" send="mount /dev/vda /root; cp -r /etc /root; ls /root/etc; umount /root" verbose="yes"/>
<route>
<service name="Terminal"> <child name="terminal_crosslink"/> </service>
<any-service><parent/><any-child/></any-service>
@ -96,7 +116,7 @@ if { [have_spec arm] } {
if {![file exists bin/dtb]} {
puts "Download device tree blob ..."
exec >& /dev/null wget -c -O bin/dtb http://genode.org/files/release-20.05/dtb-arm32-virt
exec >& /dev/null wget -c -O bin/dtb http://genode.org/files/release-21.02/dtb-arm32-virt
}
if {![file exists bin/initrd]} {
@ -153,7 +173,7 @@ if { [have_spec arm_64] } {
if {![file exists bin/dtb]} {
puts "Download device tree blob ..."
exec >& /dev/null wget -c -O bin/dtb http://genode.org/files/release-20.11/dtb-arm64-virt-smp
exec >& /dev/null wget -c -O bin/dtb http://genode.org/files/release-21.02/dtb-arm64-virt-smp
}
if {![file exists bin/initrd]} {
@ -200,23 +220,21 @@ if { [have_spec arm_64] } {
# find . | cpio -H newc -o | gzip > ../initrd
}
set boot_modules {
core ld.lib.so init
timer
terminal_crosslink
catch { exec [installed_command dd] if=/dev/zero of=bin/block.img bs=1M count=0 seek=64 }
exec [installed_command mkfs.vfat] bin/block.img
build_boot_image {
test-terminal_expect_send
nic_router
log_terminal
vmm
linux
dtb
initrd
block.img
}
build_boot_image $boot_modules
#
# Execute test case
#
append qemu_args " -nographic "
run_genode_until "\[init -> vm\] .*sbin.*" 220
exec rm bin/linux bin/dtb bin/initrd
run_genode_until "\[init -> vm\] .*resolv.conf.*" 220
exec rm bin/linux bin/dtb bin/initrd bin/block.img

View File

@ -41,6 +41,10 @@ namespace Vmm {
VIRTIO_NET_MMIO_SIZE = 0x200,
VIRTIO_NET_IRQ = 49,
VIRTIO_BLK_MMIO_START = 0xa000400,
VIRTIO_BLK_MMIO_SIZE = 0x200,
VIRTIO_BLK_IRQ = 50,
RAM_START = 0x40000000,
RAM_SIZE = 128 * 1024 *1024,

View File

@ -86,4 +86,11 @@
dma-coherent;
reg = <0x00 0xa000200 0x00 0x200>;
};
virtio_mmio@a000400 {
interrupts = <0x00 0x12 0x01>;
compatible = "virtio,mmio";
dma-coherent;
reg = <0x00 0xa000400 0x00 0x200>;
};
};

View File

@ -41,6 +41,10 @@ namespace Vmm {
VIRTIO_NET_MMIO_SIZE = 0x200,
VIRTIO_NET_IRQ = 49,
VIRTIO_BLK_MMIO_START = 0xa000400,
VIRTIO_BLK_MMIO_SIZE = 0x200,
VIRTIO_BLK_IRQ = 50,
RAM_START = 0x40000000,
RAM_SIZE = 128 * 1024 *1024,

View File

@ -114,4 +114,11 @@
dma-coherent;
reg = <0x00 0xa000200 0x00 0x200>;
};
virtio_mmio@a000400 {
interrupts = <0x00 0x12 0x01>;
compatible = "virtio,mmio";
dma-coherent;
reg = <0x00 0xa000400 0x00 0x200>;
};
};

View File

@ -0,0 +1,284 @@
/*
* \brief Virtio Block implementation
* \author Stefan Kalkowski
* \date 2020-12-22
*/
/*
* 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.
*/
#ifndef _VIRTIO_BLOCK_H_
#define _VIRTIO_BLOCK_H_
#include <base/log.h>
#include <block_session/connection.h>
#include <virtio_device.h>
namespace Vmm
{
class Virtio_block_queue;
class Virtio_block_request;
class Virtio_block_device;
using namespace Genode;
}
class Vmm::Virtio_block_queue : public Virtio_split_queue
{
private:
Ring_index _used_idx;
friend class Virtio_block_request;
friend class Virtio_block_device;
public:
using Virtio_split_queue::Virtio_split_queue;
template <typename FUNC>
bool notify(FUNC func)
{
memory_barrier();
for (Ring_index avail_idx = _avail.current();
_cur_idx != avail_idx; _cur_idx.inc()) {
func(_avail.get(_cur_idx), _descriptors, _ram);
}
return false;
}
void ack(Descriptor_index id, size_t written)
{
_used.add(_used_idx, id, written);
_used_idx.inc();
_used.write<Used_queue::Idx>(_used_idx.idx());
memory_barrier();
}
};
class Vmm::Virtio_block_request
{
public:
struct Invalid_request : Genode::Exception {};
private:
using Index = Virtio_block_queue::Descriptor_index;
using Descriptor = Virtio_block_queue::Descriptor;
using Descriptor_array = Virtio_block_queue::Descriptor_array;
struct Request
{
enum Type {
READ = 0,
WRITE = 1,
FLUSH = 4,
DISCARD = 11,
WRITE_ZEROES = 13,
};
uint32_t type;
uint32_t reserved;
uint64_t sector;
};
enum Status { OK, IO_ERROR, UNSUPPORTED };
Index _next(Descriptor & desc) {
if (!Descriptor::Flags::Next::get(desc.flags())) {
throw Invalid_request(); }
return desc.next();
}
Descriptor_array & _array;
Ram & _ram;
template <typename T>
T * _desc_addr(Descriptor const & desc) const {
return (T*) _ram.local_address(desc.address(),
desc.length()); }
Index _request_idx;
Descriptor _request { _array.get(_request_idx) };
Request & _vbr { *_desc_addr<Request>(_request) };
Index _data_idx { _next(_request) };
Descriptor _data { _array.get(_data_idx) };
Index _status_idx { _next(_data) };
Descriptor _status { _array.get(_status_idx) };
size_t _written { 0 };
bool _write() const { return _vbr.type == Request::WRITE; }
public:
Virtio_block_request(Index id,
Descriptor_array & array,
Ram & ram)
: _array(array), _ram(ram), _request_idx(id)
{
if (_request.length() != sizeof(Request) ||
_status.length() != sizeof(uint8_t)) {
throw Invalid_request(); }
}
Block::Operation const operation(Block::Session::Info & info) const
{
if (_vbr.type != Request::READ && _vbr.type != Request::WRITE) {
throw Invalid_request(); }
size_t sz = _data.length();
Block::Operation const op {
.type = _write() ? Block::Operation::Type::WRITE
: Block::Operation::Type::READ,
.block_number = (_vbr.sector * 512) / info.block_size,
.count = (sz<info.block_size) ? 1 : (sz/info.block_size)
};
return op;
}
void * address() const { return _desc_addr<void>(_data); }
size_t size() const { return _data.length(); }
void written_to_descriptor(size_t sz) { _written = sz; }
void done(Virtio_block_queue & queue)
{
*_desc_addr<uint8_t>(_status) = OK;
queue.ack(_request_idx, _written);
}
};
class Vmm::Virtio_block_device
: public Virtio_device<Virtio_block_queue, 1>
{
private:
using Index = Virtio_block_queue::Descriptor_index;
using Descriptor_array = Virtio_block_queue::Descriptor_array;
enum Queue_idx { REQUEST };
enum { BLOCK_BUFFER_SIZE = 1024*1024 };
struct Job : Virtio_block_request,
Block::Connection<Job>::Job
{
Job(Block::Connection<Job> & con,
Block::Session::Info & info,
Virtio_block_device::Index id,
Virtio_block_device::Descriptor_array & array,
Ram & ram)
: Virtio_block_request(id, array, ram),
Block::Connection<Job>::Job(con, Virtio_block_request::operation(info)) {}
};
Heap & _heap;
Allocator_avl _block_alloc { &_heap };
Block::Connection<Job> _block;
Block::Session::Info _block_info { _block.info() };
Cpu::Signal_handler<Virtio_block_device> _handler;
void _block_signal()
{
Genode::Mutex::Guard guard(_mutex);
_block.update_jobs(*this);
}
void _notify(unsigned idx) override
{
auto lambda = [&] (Index id,
Descriptor_array & array,
Ram & ram)
{
try {
new (_heap) Job(_block, _block_info, id, array, ram);
_block.update_jobs(*this);
} catch(Virtio_block_request::Invalid_request &) {
error("Invalid block request ignored!");
}
return 0;
};
_queue[REQUEST]->notify(lambda);
}
enum Device_id { BLOCK = 2 };
struct Configuration_area : Mmio_register
{
uint64_t capacity;
Register read(Address_range& range, Cpu&) override
{
if (range.start == 0 && range.size == 4)
return capacity & 0xffffffff;
if (range.start == 4 && range.size == 4)
return capacity >> 32;
throw Exception("Invalid read access of configuration area ",
range);
}
Configuration_area(Virtio_block_device & device, uint64_t capacity)
: Mmio_register("Configuration_area", Mmio_register::RO, 0x100, 8),
capacity(capacity) { device.add(*this); }
} _config_area{ *this, _block_info.block_count *
(_block_info.block_size / 512) };
public:
Virtio_block_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)
: Virtio_device<Virtio_block_queue,1>(name, addr, size,
irq, cpu, bus, ram, BLOCK),
_heap(heap),
_block(env, &_block_alloc, BLOCK_BUFFER_SIZE),
_handler(cpu, env.ep(), *this, &Virtio_block_device::_block_signal) {
_block.sigh(_handler); }
/*****************************************************
** Block::Connection::Update_jobs_policy interface **
*****************************************************/
void produce_write_content(Job & job, off_t offset,
char *dst, size_t length)
{
size_t sz = Genode::min(length,job.size());
memcpy(dst, job.address(), sz);
job.written_to_descriptor(sz);
}
void consume_read_result(Job & job, off_t offset,
char const *src, size_t length)
{
size_t sz = Genode::min(length,job.size());
memcpy(job.address(), src, sz);
}
void completed(Job &job, bool success)
{
job.done(*_queue[REQUEST]);
_assert_irq();
destroy(_heap, &job);
}
};
#endif /* _VIRTIO_BLOCK_H_ */

View File

@ -55,7 +55,9 @@ Vm::Vm(Genode::Env & env)
_virtio_console("HVC", VIRTIO_CONSOLE_MMIO_START, VIRTIO_CONSOLE_MMIO_SIZE,
VIRTIO_CONSOLE_IRQ, boot_cpu(), _bus, _ram, env),
_virtio_net("Net", VIRTIO_NET_MMIO_START, VIRTIO_NET_MMIO_SIZE,
VIRTIO_NET_IRQ, boot_cpu(), _bus, _ram, env)
VIRTIO_NET_IRQ, boot_cpu(), _bus, _ram, env),
_virtio_block("Block", VIRTIO_BLK_MMIO_START, VIRTIO_BLK_MMIO_SIZE,
VIRTIO_BLK_IRQ, boot_cpu(), _bus, _ram, env, _heap)
{
_vm.attach(_vm_ram.cap(), RAM_START);

View File

@ -22,6 +22,7 @@
#include <pl011.h>
#include <virtio_console.h>
#include <virtio_net.h>
#include <virtio_block.h>
#include <base/attached_ram_dataspace.h>
#include <base/attached_rom_dataspace.h>
@ -54,6 +55,7 @@ class Vmm::Vm
Pl011 _uart;
Virtio_console _virtio_console;
Virtio_net _virtio_net;
Virtio_block_device _virtio_block;
void _load_kernel();
void _load_dtb();