mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-24 07:46:42 +00:00
7fbb245710
To simplify writing native VirtIO drivers for Genode add helper classes representing VirtIO device and queue. The queue implementation should be platform independant. The device abstraction however is closely tied to the VirtIO transport being used (PCI/MMIO). Both PCI and MMIO implementations expose the same public API so the actual driver logic should be the same regardless of which transport is used. Its also important to note that the PCI version of Virtio::Device currently does not support MSI-X interrupts. Unfortunately my kowledge about PCI bus is very limited and my main area of interest was to get VirtIO drivers working on virt_qemu ARM/Aarch64 platform. As such all the VirtIO drivers I plan to submit will work with PCI bus, but might not use some extended capabilities. Ref #3825
202 lines
5.4 KiB
C++
202 lines
5.4 KiB
C++
/*
|
|
* \brief VirtIO MMIO device
|
|
* \author Piotr Tworek
|
|
* \date 2019-09-27
|
|
*/
|
|
|
|
/*
|
|
* 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 _INCLUDE__VIRTIO__MMIO_DEVICE_H_
|
|
#define _INCLUDE__VIRTIO__MMIO_DEVICE_H_
|
|
|
|
#include <base/attached_dataspace.h>
|
|
#include <os/attached_mmio.h>
|
|
#include <util/mmio.h>
|
|
#include <virtio/queue.h>
|
|
|
|
|
|
namespace Virtio {
|
|
using namespace Genode;
|
|
class Device;
|
|
}
|
|
|
|
|
|
class Virtio::Device : Genode::Attached_dataspace, Genode::Mmio
|
|
{
|
|
public:
|
|
|
|
struct Invalid_device : Genode::Exception { };
|
|
|
|
enum Status : uint8_t
|
|
{
|
|
RESET = 0,
|
|
ACKNOWLEDGE = 1 << 0,
|
|
DRIVER = 1 << 1,
|
|
DRIVER_OK = 1 << 2,
|
|
FEATURES_OK = 1 << 3,
|
|
FAILED = 1 << 7,
|
|
};
|
|
|
|
enum Access_size : uint8_t
|
|
{
|
|
ACCESS_8BIT,
|
|
ACCESS_16BIT,
|
|
ACCESS_32BIT,
|
|
};
|
|
|
|
private:
|
|
|
|
enum { VIRTIO_MMIO_MAGIC = 0x74726976 };
|
|
|
|
/**
|
|
* Some of the registers are actually 8 bits wide, but according to
|
|
* section 4.2.2.2 of VIRTIO 1.0 spec "The driver MUST use only 32 bit
|
|
* wide and aligned reads and writes".
|
|
*/
|
|
struct Magic : Register<0x000, 32> { };
|
|
struct Version : Register<0x004, 32> { };
|
|
struct DeviceID : Register<0x008, 32> { };
|
|
struct VendorID : Register<0x00C, 32> { };
|
|
struct DeviceFeatures : Register<0x010, 32> { };
|
|
struct DeviceFeaturesSel : Register<0x014, 32> { };
|
|
struct DriverFeatures : Register<0x020, 32> { };
|
|
struct DriverFeaturesSel : Register<0x024, 32> { };
|
|
struct QueueSel : Register<0x030, 32> { };
|
|
struct QueueNumMax : Register<0x034, 32> { };
|
|
struct QueueNum : Register<0x038, 32> { };
|
|
struct QueueReady : Register<0x044, 32> { };
|
|
struct QueueNotify : Register<0x050, 32> { };
|
|
struct InterruptStatus : Register<0x060, 32> { };
|
|
struct InterruptAck : Register<0x064, 32> { };
|
|
struct StatusReg : Register<0x070, 32> { };
|
|
struct QueueDescLow : Register<0x080, 32> { };
|
|
struct QueueDescHigh : Register<0x084, 32> { };
|
|
struct QueueAvailLow : Register<0x090, 32> { };
|
|
struct QueueAvailHigh : Register<0x094, 32> { };
|
|
struct QueueUsedLow : Register<0x0A0, 32> { };
|
|
struct QueueUsedHigh : Register<0x0A4, 32> { };
|
|
struct ConfigGeneration : Register<0x0FC, 32> { };
|
|
|
|
/**
|
|
* Different views on device configuration space. According to the
|
|
* VIRTIO 1.0 spec 64 bit wide registers are supposed to be read as
|
|
* two 32 bit values.
|
|
*/
|
|
struct Config_8 : Register_array<0x100, 8, 256, 8> { };
|
|
struct Config_16 : Register_array<0x100, 16, 128, 16> { };
|
|
struct Config_32 : Register_array<0x100, 32, 64, 32> { };
|
|
|
|
/*
|
|
* Noncopyable
|
|
*/
|
|
Device(Device const &) = delete;
|
|
Device &operator = (Device const &) = delete;
|
|
|
|
public:
|
|
|
|
Device(Genode::Env &env,
|
|
Genode::Io_mem_dataspace_capability io_mem_ds,
|
|
size_t offset)
|
|
: Attached_dataspace(env.rm(), io_mem_ds)
|
|
, Mmio((addr_t)local_addr<void>() + offset)
|
|
{
|
|
if (read<Magic>() != VIRTIO_MMIO_MAGIC) {
|
|
throw Invalid_device(); }
|
|
}
|
|
|
|
uint32_t vendor_id() { return read<VendorID>(); }
|
|
uint32_t device_id() { return read<DeviceID>(); }
|
|
uint8_t get_status() { return read<StatusReg>() & 0xff; }
|
|
|
|
bool set_status(uint8_t status)
|
|
{
|
|
write<StatusReg>(status);
|
|
return read<StatusReg>() == status;
|
|
}
|
|
|
|
uint32_t get_features(uint32_t selection)
|
|
{
|
|
write<DeviceFeaturesSel>(selection);
|
|
return read<DeviceFeatures>();
|
|
}
|
|
|
|
void set_features(uint32_t selection, uint32_t features)
|
|
{
|
|
write<DriverFeaturesSel>(selection);
|
|
write<DriverFeatures>(features);
|
|
}
|
|
|
|
uint8_t get_config_generation() {
|
|
return read<ConfigGeneration>() & 0xff; }
|
|
|
|
uint16_t get_max_queue_size(uint16_t queue_index)
|
|
{
|
|
write<QueueSel>(queue_index);
|
|
if (read<QueueReady>() != 0) {
|
|
return 0; }
|
|
return read<QueueNumMax>();
|
|
}
|
|
|
|
uint32_t read_config(uint8_t offset, Access_size size)
|
|
{
|
|
switch (size) {
|
|
case ACCESS_8BIT: return read<Config_8>(offset);
|
|
case ACCESS_16BIT: return read<Config_16>(offset >> 1);
|
|
case ACCESS_32BIT: return read<Config_32>(offset >> 2);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void write_config(uint8_t offset, Access_size size, uint32_t value)
|
|
{
|
|
switch (size) {
|
|
case ACCESS_8BIT: write<Config_8>(value, offset); break;
|
|
case ACCESS_16BIT: write<Config_16>(value, (offset >> 1)); break;
|
|
case ACCESS_32BIT: write<Config_32>(value, (offset >> 2)); break;
|
|
}
|
|
}
|
|
|
|
bool configure_queue(uint16_t queue_index,
|
|
Virtio::Queue_description desc)
|
|
{
|
|
write<QueueSel>(queue_index);
|
|
|
|
if (read<QueueReady>() != 0)
|
|
return false;
|
|
|
|
write<QueueNum>(desc.size);
|
|
|
|
uint64_t addr = desc.desc;
|
|
write<QueueDescLow>((uint32_t)addr);
|
|
write<QueueDescHigh>((uint32_t)(addr >> 32));
|
|
|
|
addr = desc.avail;
|
|
write<QueueAvailLow>((uint32_t)addr);
|
|
write<QueueAvailHigh>((uint32_t)(addr >> 32));
|
|
|
|
addr = desc.used;
|
|
write<QueueUsedLow>((uint32_t)addr);
|
|
write<QueueUsedHigh>((uint32_t)(addr >> 32));
|
|
|
|
write<QueueReady>(1);
|
|
return read<QueueReady>() != 0;
|
|
}
|
|
|
|
void notify_buffers_available(uint16_t queue_index) {
|
|
write<QueueNotify>(queue_index); }
|
|
|
|
uint32_t read_isr()
|
|
{
|
|
uint32_t isr = read<InterruptStatus>();
|
|
write<InterruptAck>(isr);
|
|
return isr;
|
|
}
|
|
};
|
|
|
|
#endif /* _INCLUDE__VIRTIO__MMIO_DEVICE_H_ */
|