From 62303d46d1c3ecf097e177f68f0a7c5094426f45 Mon Sep 17 00:00:00 2001 From: Idan Horowitz Date: Sat, 2 Jan 2021 19:53:05 +0200 Subject: [PATCH] Kernel: Add base support for VirtIO devices Based on pull #3236 by tomuta, this adds helper methods for generic device initialization, and partily-broken virtqueue helper methods Co-authored-by: Tom Co-authored-by: Sahan --- Kernel/CMakeLists.txt | 2 + Kernel/Debug.h.in | 4 + Kernel/PCI/Access.cpp | 4 +- Kernel/VirtIO/VirtIO.cpp | 362 ++++++++++++++++++++++++++ Kernel/VirtIO/VirtIO.h | 249 ++++++++++++++++++ Kernel/VirtIO/VirtIOQueue.cpp | 106 ++++++++ Kernel/VirtIO/VirtIOQueue.h | 103 ++++++++ Kernel/init.cpp | 3 + Meta/CMake/all_the_debug_macros.cmake | 1 + 9 files changed, 832 insertions(+), 2 deletions(-) create mode 100644 Kernel/VirtIO/VirtIO.cpp create mode 100644 Kernel/VirtIO/VirtIO.h create mode 100644 Kernel/VirtIO/VirtIOQueue.cpp create mode 100644 Kernel/VirtIO/VirtIOQueue.h diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 4349b51bcd..a0b3417b0a 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -40,6 +40,8 @@ set(KERNEL_SOURCES Devices/SB16.cpp Devices/SerialDevice.cpp Devices/USB/UHCIController.cpp + VirtIO/VirtIO.cpp + VirtIO/VirtIOQueue.cpp Devices/VMWareBackdoor.cpp Devices/ZeroDevice.cpp Devices/HID/I8042Controller.cpp diff --git a/Kernel/Debug.h.in b/Kernel/Debug.h.in index 6858889c81..98dc872f46 100644 --- a/Kernel/Debug.h.in +++ b/Kernel/Debug.h.in @@ -314,6 +314,10 @@ #cmakedefine01 VOLATILE_PAGE_RANGES_DEBUG #endif +#ifndef VIRTIO_DEBUG +#cmakedefine01 VIRTIO_DEBUG +#endif + #ifndef VRA_DEBUG #cmakedefine01 VRA_DEBUG #endif diff --git a/Kernel/PCI/Access.cpp b/Kernel/PCI/Access.cpp index 763e0aadc5..3f007f630c 100644 --- a/Kernel/PCI/Access.cpp +++ b/Kernel/PCI/Access.cpp @@ -254,7 +254,7 @@ u32 get_BAR5(Address address) u32 get_BAR(Address address, u8 bar) { - ASSERT(bar <= 5); + VERIFY(bar <= 5); switch (bar) { case 0: return get_BAR0(address); @@ -269,7 +269,7 @@ u32 get_BAR(Address address, u8 bar) case 5: return get_BAR5(address); default: - ASSERT_NOT_REACHED(); + VERIFY_NOT_REACHED(); } } diff --git a/Kernel/VirtIO/VirtIO.cpp b/Kernel/VirtIO/VirtIO.cpp new file mode 100644 index 0000000000..637b079658 --- /dev/null +++ b/Kernel/VirtIO/VirtIO.cpp @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +namespace Kernel { + +void VirtIO::detect() +{ + PCI::enumerate([&](const PCI::Address& address, PCI::ID id) { + if (address.is_null() || id.is_null()) + return; + if (id.vendor_id != VIRTIO_PCI_VENDOR_ID) + return; + }); +} + +VirtIODevice::VirtIODevice(PCI::Address address, const char* class_name) + : PCI::Device(address, PCI::get_interrupt_line(address)) + , m_class_name(class_name) + , m_io_base(IOAddress(PCI::get_BAR0(pci_address()) & ~1)) +{ + dbgln("{}: Found @ {}", m_class_name, pci_address()); + + enable_bus_mastering(pci_address()); + + reset_device(); + set_status_bit(DEVICE_STATUS_ACKNOWLEDGE); + + auto capabilities = PCI::get_physical_id(address).capabilities(); + for (auto& capability : capabilities) { + if (capability.id() == PCI_CAPABILITY_VENDOR_SPECIFIC) { + // We have a virtio_pci_cap + Configuration cfg = {}; + cfg.cfg_type = capability.read8(0x3); + switch (cfg.cfg_type) { + case VIRTIO_PCI_CAP_COMMON_CFG: + case VIRTIO_PCI_CAP_NOTIFY_CFG: + case VIRTIO_PCI_CAP_ISR_CFG: + case VIRTIO_PCI_CAP_DEVICE_CFG: + case VIRTIO_PCI_CAP_PCI_CFG: { + auto cap_length = capability.read8(0x2); + if (cap_length < 0x10) { + dbgln("{}: Unexpected capability size: {}", m_class_name, cap_length); + break; + } + cfg.bar = capability.read8(0x4); + if (cfg.bar > 0x5) { + dbgln("{}: Unexpected capability bar value: {}", m_class_name, cfg.bar); + break; + } + cfg.offset = capability.read32(0x8); + cfg.length = capability.read32(0xc); + dbgln_if(VIRTIO_DEBUG, "{}: Found configuration {}, bar: {}, offset: {}, length: {}", m_class_name, cfg.cfg_type, cfg.bar, cfg.offset, cfg.length); + m_configs.append(cfg); + + if (cfg.cfg_type == VIRTIO_PCI_CAP_COMMON_CFG) + m_use_mmio = true; + else if (cfg.cfg_type == VIRTIO_PCI_CAP_NOTIFY_CFG) + m_notify_multiplier = capability.read32(0x10); + break; + } + default: + dbgln("{}: Unknown capability configuration type: {}", m_class_name, cfg.cfg_type); + break; + } + } + } + + m_common_cfg = get_config(VIRTIO_PCI_CAP_COMMON_CFG, 0); + m_notify_cfg = get_config(VIRTIO_PCI_CAP_NOTIFY_CFG, 0); + m_isr_cfg = get_config(VIRTIO_PCI_CAP_ISR_CFG, 0); + + set_status_bit(DEVICE_STATUS_DRIVER); +} + +VirtIODevice::~VirtIODevice() +{ +} + +auto VirtIODevice::mapping_for_bar(u8 bar) -> MappedMMIO& +{ + VERIFY(m_use_mmio); + auto& mapping = m_mmio[bar]; + if (!mapping.base) { + mapping.size = PCI::get_BAR_space_size(pci_address(), bar); + mapping.base = MM.allocate_kernel_region(PhysicalAddress(page_base_of(PCI::get_BAR(pci_address(), bar))), page_round_up(mapping.size), "VirtIO MMIO", Region::Access::Read | Region::Access::Write, Region::Cacheable::No); + if (!mapping.base) + dbgln("{}: Failed to map bar {}", m_class_name, bar); + } + return mapping; +} + +void VirtIODevice::notify_queue(u16 queue_index) +{ + dbgln("VirtIODevice: notifying about queue change at idx: {}", queue_index); + if (!m_use_mmio) + out(REG_QUEUE_NOTIFY, queue_index); + else + config_write16(m_notify_cfg, get_queue(queue_index)->notify_offset() * m_notify_multiplier, queue_index); +} + +u8 VirtIODevice::config_read8(const Configuration* config, u32 offset) +{ + return mapping_for_bar(config->bar).read(config->offset + offset); +} + +u16 VirtIODevice::config_read16(const Configuration* config, u32 offset) +{ + return mapping_for_bar(config->bar).read(config->offset + offset); +} + +u32 VirtIODevice::config_read32(const Configuration* config, u32 offset) +{ + return mapping_for_bar(config->bar).read(config->offset + offset); +} + +void VirtIODevice::config_write8(const Configuration* config, u32 offset, u8 value) +{ + mapping_for_bar(config->bar).write(config->offset + offset, value); +} + +void VirtIODevice::config_write16(const Configuration* config, u32 offset, u16 value) +{ + mapping_for_bar(config->bar).write(config->offset + offset, value); +} + +void VirtIODevice::config_write32(const Configuration* config, u32 offset, u32 value) +{ + mapping_for_bar(config->bar).write(config->offset + offset, value); +} + +void VirtIODevice::config_write64(const Configuration* config, u32 offset, u64 value) +{ + mapping_for_bar(config->bar).write(config->offset + offset, value); +} + +u8 VirtIODevice::read_status_bits() +{ + if (!m_use_mmio) + return in(REG_DEVICE_STATUS); + return config_read8(m_common_cfg, COMMON_CFG_DEVICE_STATUS); +} + +void VirtIODevice::clear_status_bit(u8 status_bit) +{ + m_status &= status_bit; + if (!m_use_mmio) + out(REG_DEVICE_STATUS, m_status); + else + config_write8(m_common_cfg, COMMON_CFG_DEVICE_STATUS, m_status); +} + +void VirtIODevice::set_status_bit(u8 status_bit) +{ + m_status |= status_bit; + if (!m_use_mmio) + out(REG_DEVICE_STATUS, m_status); + else + config_write8(m_common_cfg, COMMON_CFG_DEVICE_STATUS, m_status); +} + +u64 VirtIODevice::get_device_features() +{ + if (!m_use_mmio) + return in(REG_DEVICE_FEATURES); + config_write32(m_common_cfg, COMMON_CFG_DEVICE_FEATURE_SELECT, 0); + auto lower_bits = config_read32(m_common_cfg, COMMON_CFG_DEVICE_FEATURE); + config_write32(m_common_cfg, COMMON_CFG_DEVICE_FEATURE_SELECT, 1); + u64 upper_bits = (u64)config_read32(m_common_cfg, COMMON_CFG_DEVICE_FEATURE) << 32; + return upper_bits | lower_bits; +} + +bool VirtIODevice::accept_device_features(u64 device_features, u64 accepted_features) +{ + VERIFY(!m_did_accept_features); + m_did_accept_features = true; + + if (is_feature_set(device_features, VIRTIO_F_VERSION_1)) { + accepted_features |= VIRTIO_F_VERSION_1; + } else { + dbgln("{}: legacy device detected", m_class_name); + } + + if (is_feature_set(device_features, VIRTIO_F_RING_PACKED)) { + dbgln("{}: packed queues not yet supported", m_class_name); + accepted_features &= ~(VIRTIO_F_RING_PACKED); + } + + dbgln("VirtIOConsole: Device features: {}", device_features); + dbgln("VirtIOConsole: Accepted features: {}", accepted_features); + + if (!m_use_mmio) { + out(REG_GUEST_FEATURES, accepted_features); + } else { + config_write32(m_common_cfg, COMMON_CFG_DRIVER_FEATURE_SELECT, 0); + config_write32(m_common_cfg, COMMON_CFG_DRIVER_FEATURE, accepted_features); + config_write32(m_common_cfg, COMMON_CFG_DRIVER_FEATURE_SELECT, 1); + config_write32(m_common_cfg, COMMON_CFG_DRIVER_FEATURE, accepted_features >> 32); + } + set_status_bit(DEVICE_STATUS_FEATURES_OK); + m_status = read_status_bits(); + if (!(m_status & DEVICE_STATUS_FEATURES_OK)) { + set_status_bit(DEVICE_STATUS_FAILED); + dbgln("{}: Features not accepted by host!", m_class_name); + return false; + } + + m_accepted_features = accepted_features; + dbgln_if(VIRTIO_DEBUG, "{}: Features accepted by host", m_class_name); + return true; +} + +auto VirtIODevice::get_common_config(u32 index) const -> const Configuration* +{ + if (index == 0) + return m_common_cfg; + return get_config(VIRTIO_PCI_CAP_COMMON_CFG, index); +} + +auto VirtIODevice::get_device_config(u32 index) const -> const Configuration* +{ + return get_config(VIRTIO_PCI_CAP_DEVICE_CFG, index); +} + +void VirtIODevice::reset_device() +{ + dbgln_if(VIRTIO_DEBUG, "{}: Reset device", m_class_name); + if (!m_use_mmio) { + clear_status_bit(0); + while (read_status_bits() != 0) { + // TODO: delay a bit? + } + return; + } else if (m_common_cfg) { + config_write8(m_common_cfg, COMMON_CFG_DEVICE_STATUS, 0); + while (config_read8(m_common_cfg, COMMON_CFG_DEVICE_STATUS) != 0) { + // TODO: delay a bit? + } + return; + } + dbgln_if(VIRTIO_DEBUG, "{}: No handle to device, cant reset", m_class_name); +} + +bool VirtIODevice::setup_queue(u16 queue_index) +{ + if (!m_use_mmio || !m_common_cfg) + return false; + + config_write16(m_common_cfg, COMMON_CFG_QUEUE_SELECT, queue_index); + u16 queue_size = config_read16(m_common_cfg, COMMON_CFG_QUEUE_SIZE); + if (queue_size == 0) { + dbgln_if(VIRTIO_DEBUG, "{}: Queue[{}] is unavailable!", m_class_name, queue_index); + return true; + } + + u16 queue_notify_offset = config_read16(m_common_cfg, COMMON_CFG_QUEUE_NOTIFY_OFF); + + auto queue = make(queue_size, queue_notify_offset); + if (queue->is_null()) + return false; + + config_write64(m_common_cfg, COMMON_CFG_QUEUE_DESC, queue->descriptor_area().get()); + config_write64(m_common_cfg, COMMON_CFG_QUEUE_DRIVER, queue->driver_area().get()); + config_write64(m_common_cfg, COMMON_CFG_QUEUE_DEVICE, queue->device_area().get()); + + dbgln_if(VIRTIO_DEBUG, "{}: Queue[{}] size: {}", m_class_name, queue_index, queue_size); + + m_queues.append(move(queue)); + return true; +} + +void VirtIODevice::set_requested_queue_count(u16 count) +{ + m_queue_count = count; +} + +bool VirtIODevice::setup_queues() +{ + if (m_common_cfg) { + auto maximum_queue_count = config_read16(m_common_cfg, COMMON_CFG_NUM_QUEUES); + if (m_queue_count == 0) { + m_queue_count = maximum_queue_count; + } else if (m_queue_count > maximum_queue_count) { + dbgln("{}: {} queues requested but only {} available!", m_class_name, m_queue_count, maximum_queue_count); + return false; + } + } + + dbgln_if(VIRTIO_DEBUG, "{}: Setting up {} queues", m_class_name, m_queue_count); + for (u16 i = 0; i < m_queue_count; i++) { + if (!setup_queue(i)) + return false; + } + return true; +} + +bool VirtIODevice::finish_init() +{ + VERIFY(m_did_accept_features); + VERIFY(!(m_status & DEVICE_STATUS_DRIVER_OK)); + if (!setup_queues()) { + dbgln("{}: Failed to setup queues", m_class_name); + return false; + } + set_status_bit(DEVICE_STATUS_DRIVER_OK); + dbgln_if(VIRTIO_DEBUG, "{}: Finished initialization", m_class_name); + return true; +} + +void VirtIODevice::supply_buffer_and_notify(u16 queue_index, const u8* buffer, u32 len, BufferType buffer_type) +{ + VERIFY(queue_index < m_queue_count); + if (get_queue(queue_index)->supply_buffer(buffer, len, buffer_type)) + notify_queue(queue_index); +} + +u8 VirtIODevice::isr_status() +{ + if (!m_use_mmio) + return in(REG_ISR_STATUS); + return config_read8(m_isr_cfg, 0); +} + +void VirtIODevice::handle_irq(const RegisterState&) +{ + u8 isr_type = isr_status(); + dbgln_if(VIRTIO_DEBUG, "VirtIODevice: Handling interrupt with status: {}", isr_type); + if (isr_type & DEVICE_CONFIG_INTERRUPT) + handle_device_config_change(); + if (isr_type & QUEUE_INTERRUPT) { + for (auto& queue : m_queues) { + if (queue.handle_interrupt()) + return; + } + } +} + +} diff --git a/Kernel/VirtIO/VirtIO.h b/Kernel/VirtIO/VirtIO.h new file mode 100644 index 0000000000..1730a3e0ce --- /dev/null +++ b/Kernel/VirtIO/VirtIO.h @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Kernel { + +#define VIRTIO_PCI_VENDOR_ID 0x1AF4 + +#define REG_DEVICE_FEATURES 0x0 +#define REG_GUEST_FEATURES 0x4 +#define REG_QUEUE_ADDRESS 0x8 +#define REG_QUEUE_SIZE 0xc +#define REG_QUEUE_SELECT 0xe +#define REG_QUEUE_NOTIFY 0x10 +#define REG_DEVICE_STATUS 0x12 +#define REG_ISR_STATUS 0x13 + +#define DEVICE_STATUS_ACKNOWLEDGE (1 << 0) +#define DEVICE_STATUS_DRIVER (1 << 1) +#define DEVICE_STATUS_DRIVER_OK (1 << 2) +#define DEVICE_STATUS_FEATURES_OK (1 << 3) +#define DEVICE_STATUS_DEVICE_NEEDS_RESET (1 << 6) +#define DEVICE_STATUS_FAILED (1 << 7) + +#define VIRTIO_F_VERSION_1 ((u64)1 << 32) +#define VIRTIO_F_RING_PACKED ((u64)1 << 34) + +#define VIRTIO_PCI_CAP_COMMON_CFG 1 +#define VIRTIO_PCI_CAP_NOTIFY_CFG 2 +#define VIRTIO_PCI_CAP_ISR_CFG 3 +#define VIRTIO_PCI_CAP_DEVICE_CFG 4 +#define VIRTIO_PCI_CAP_PCI_CFG 5 + +// virtio_pci_common_cfg +#define COMMON_CFG_DEVICE_FEATURE_SELECT 0x0 +#define COMMON_CFG_DEVICE_FEATURE 0x4 +#define COMMON_CFG_DRIVER_FEATURE_SELECT 0x8 +#define COMMON_CFG_DRIVER_FEATURE 0xc +#define COMMON_CFG_MSIX_CONFIG 0x10 +#define COMMON_CFG_NUM_QUEUES 0x12 +#define COMMON_CFG_DEVICE_STATUS 0x14 +#define COMMON_CFG_CONFIG_GENERATION 0x15 +#define COMMON_CFG_QUEUE_SELECT 0x16 +#define COMMON_CFG_QUEUE_SIZE 0x18 +#define COMMON_CFG_QUEUE_MSIX_VECTOR 0x1a +#define COMMON_CFG_QUEUE_ENABLE 0x1c +#define COMMON_CFG_QUEUE_NOTIFY_OFF 0x1e +#define COMMON_CFG_QUEUE_DESC 0x20 +#define COMMON_CFG_QUEUE_DRIVER 0x28 +#define COMMON_CFG_QUEUE_DEVICE 0x30 + +#define QUEUE_INTERRUPT 0x1 +#define DEVICE_CONFIG_INTERRUPT 0x2 + +class VirtIO { +public: + static void detect(); +}; + +class VirtIODevice : public PCI::Device { +public: + VirtIODevice(PCI::Address, const char*); + virtual ~VirtIODevice() override; + +protected: + const char* const m_class_name; + + struct MappedMMIO { + OwnPtr base; + size_t size { 0 }; + + template + T read(u32 offset) const + { + if (!base) + return 0; + VERIFY(size >= sizeof(T)); + VERIFY(offset + sizeof(T) <= size); + return *(volatile T*)(base->vaddr().offset(offset).get()); + } + + template + void write(u32 offset, T value) + { + if (!base) + return; + VERIFY(size >= sizeof(T)); + VERIFY(offset + sizeof(T) <= size); + *(volatile T*)(base->vaddr().offset(offset).get()) = value; + } + }; + + struct Configuration { + u8 cfg_type; + u8 bar; + u32 offset; + u32 length; + }; + + const Configuration* get_config(u8 cfg_type, u32 index = 0) const + { + for (const auto& cfg : m_configs) { + if (cfg.cfg_type != cfg_type) + continue; + if (index > 0) { + index--; + continue; + } + return &cfg; + } + return nullptr; + } + const Configuration* get_common_config(u32 index = 0) const; + const Configuration* get_device_config(u32 index = 0) const; + + template + void read_config_atomic(F f) + { + if (m_common_cfg) { + u8 generation_before, generation_after; + do { + generation_before = config_read8(m_common_cfg, 0x15); + f(); + generation_after = config_read8(m_common_cfg, 0x15); + } while (generation_before != generation_after); + } else { + f(); + } + } + + u8 config_read8(const Configuration*, u32); + u16 config_read16(const Configuration*, u32); + u32 config_read32(const Configuration*, u32); + void config_write8(const Configuration*, u32, u8); + void config_write16(const Configuration*, u32, u16); + void config_write32(const Configuration*, u32, u32); + void config_write64(const Configuration*, u32, u64); + + auto mapping_for_bar(u8) -> MappedMMIO&; + + u8 read_status_bits(); + void clear_status_bit(u8); + void set_status_bit(u8); + u64 get_device_features(); + bool finish_init(); + + VirtIOQueue* get_queue(u16 queue_index) + { + return &m_queues[queue_index]; + } + void set_requested_queue_count(u16); + + template + bool negotiate_features(F f) + { + u64 device_features = get_device_features(); + u64 accept_features = f(device_features); + VERIFY(!(~device_features & accept_features)); + return accept_device_features(device_features, accept_features); + } + + static bool is_feature_set(u64 feature_set, u64 test_feature) + { + // features can have more than one bit + return (feature_set & test_feature) == test_feature; + } + bool is_feature_accepted(u64 feature) const + { + VERIFY(m_did_accept_features); + return is_feature_set(m_accepted_features, feature); + } + + void supply_buffer_and_notify(u16 queue_index, const u8* buffer, u32 len, BufferType); + + virtual void handle_irq(const RegisterState&) override; + virtual void handle_device_config_change() = 0; + +private: + template + void out(u16 address, T value) + { + m_io_base.offset(address).out(value); + } + + template + T in(u16 address) + { + return m_io_base.offset(address).in(); + } + + bool accept_device_features(u64 device_features, u64 accepted_features); + + bool setup_queues(); + bool setup_queue(u16 queue_index); + void notify_queue(u16 queue_index); + + void reset_device(); + + u8 isr_status(); + + NonnullOwnPtrVector m_queues; + Vector m_configs; + const Configuration* m_common_cfg { nullptr }; // Cached due to high usage + const Configuration* m_notify_cfg { nullptr }; // Cached due to high usage + const Configuration* m_isr_cfg { nullptr }; // Cached due to high usage + + IOAddress m_io_base; + MappedMMIO m_mmio[6]; + u16 m_queue_count { 0 }; + bool m_use_mmio { false }; + u8 m_status { 0 }; + u64 m_accepted_features { 0 }; + bool m_did_accept_features { false }; + u32 m_notify_multiplier { 0 }; +}; + +} diff --git a/Kernel/VirtIO/VirtIOQueue.cpp b/Kernel/VirtIO/VirtIOQueue.cpp new file mode 100644 index 0000000000..a3154888d3 --- /dev/null +++ b/Kernel/VirtIO/VirtIOQueue.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +namespace Kernel { + +VirtIOQueue::VirtIOQueue(u16 queue_size, u16 notify_offset) + : m_queue_size(queue_size) + , m_notify_offset(notify_offset) + , m_free_buffers(queue_size) +{ + size_t size_of_descriptors = sizeof(VirtIOQueueDescriptor) * queue_size; + size_t size_of_driver = sizeof(VirtIOQueueDriver) + queue_size * sizeof(u16); + size_t size_of_device = sizeof(VirtIOQueueDevice) + queue_size * sizeof(VirtIOQueueDeviceItem); + m_region = MM.allocate_contiguous_kernel_region(page_round_up(size_of_descriptors + size_of_driver + size_of_device), "VirtIO Queue", Region::Access::Read | Region::Access::Write); + if (m_region) { + // TODO: ensure alignment!!! + u8* ptr = m_region->vaddr().as_ptr(); + memset(ptr, 0, m_region->size()); + m_descriptors = reinterpret_cast(ptr); + m_driver = reinterpret_cast(ptr + size_of_descriptors); + m_device = reinterpret_cast(ptr + size_of_descriptors + size_of_driver); + + enable_interrupts(); + } +} + +VirtIOQueue::~VirtIOQueue() +{ +} + +void VirtIOQueue::enable_interrupts() +{ + m_driver->flags = 0; +} + +void VirtIOQueue::disable_interrupts() +{ + m_driver->flags = 1; +} + +bool VirtIOQueue::supply_buffer(const u8* buffer, u32 len, BufferType buffer_type) +{ + VERIFY(buffer && len > 0); + VERIFY(m_free_buffers > 0); + + auto descriptor_index = m_free_head; + m_descriptors[descriptor_index].flags = static_cast(buffer_type); + m_descriptors[descriptor_index].address = reinterpret_cast(buffer); + m_descriptors[descriptor_index].length = len; + + m_free_buffers--; + m_free_head = (m_free_head + 1) % m_queue_size; + + m_driver->rings[m_driver->index % m_queue_size] = descriptor_index; + + full_memory_barrier(); + + m_driver->index++; + + full_memory_barrier(); + + auto device_flags = m_device->flags; + dbgln("VirtIODevice: supplied buffer... should notify: {}", device_flags); + return device_flags & 1; +} +bool VirtIOQueue::new_data_available() const +{ + return m_device->index != m_used_tail; +} +bool VirtIOQueue::handle_interrupt() +{ + if (!new_data_available()) + return false; + + if (on_data_available) + on_data_available(); + return true; +} + +} diff --git a/Kernel/VirtIO/VirtIOQueue.h b/Kernel/VirtIO/VirtIOQueue.h new file mode 100644 index 0000000000..5ce7f9a16f --- /dev/null +++ b/Kernel/VirtIO/VirtIOQueue.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include + +namespace Kernel { + +enum class BufferType { + DeviceReadable = 0, + DeviceWritable = 1 +}; + +class VirtIOQueue { +public: + VirtIOQueue(u16 queue_size, u16 notify_offset); + ~VirtIOQueue(); + + bool is_null() const { return !m_region; } + u16 notify_offset() const { return m_notify_offset; } + + void enable_interrupts(); + void disable_interrupts(); + + PhysicalAddress descriptor_area() const { return to_physical(m_descriptors); } + PhysicalAddress driver_area() const { return to_physical(m_driver); } + PhysicalAddress device_area() const { return to_physical(m_device); } + + bool supply_buffer(const u8* buffer, u32 len, BufferType); + bool new_data_available() const; + + bool handle_interrupt(); + Function on_data_available; + +private: + PhysicalAddress to_physical(void* ptr) const + { + auto offset = FlatPtr(ptr) - m_region->vaddr().get(); + return m_region->physical_page(0)->paddr().offset(offset); + } + struct VirtIOQueueDescriptor { + u64 address; + u32 length; + u16 flags; + u16 next; + }; + + struct VirtIOQueueDriver { + u16 flags; + u16 index; + u16 rings[]; + }; + + struct VirtIOQueueDeviceItem { + u32 index; + u32 length; + }; + + struct VirtIOQueueDevice { + u16 flags; + u16 index; + VirtIOQueueDeviceItem rings[]; + }; + + const u16 m_queue_size; + const u16 m_notify_offset; + u16 m_free_buffers; + u16 m_free_head { 0 }; + u16 m_used_tail { 0 }; + + VirtIOQueueDescriptor* m_descriptors { nullptr }; + VirtIOQueueDriver* m_driver { nullptr }; + VirtIOQueueDevice* m_device { nullptr }; + OwnPtr m_region; + SpinLock m_lock; +}; + +} diff --git a/Kernel/init.cpp b/Kernel/init.cpp index 0c782da787..30c372abb2 100644 --- a/Kernel/init.cpp +++ b/Kernel/init.cpp @@ -72,6 +72,7 @@ #include #include #include +#include #include #include @@ -281,6 +282,8 @@ void init_stage2(void*) DMIExpose::initialize(); + VirtIO::detect(); + E1000NetworkAdapter::detect(); NE2000NetworkAdapter::detect(); RTL8139NetworkAdapter::detect(); diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index 37cadb50b0..ba76fbc8f7 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -19,6 +19,7 @@ set(VMWARE_BACKDOOR_DEBUG ON) set(FILEDESCRIPTION_DEBUG ON) set(PROCFS_DEBUG ON) set(VFS_DEBUG ON) +set(VIRTIO_DEBUG ON) set(IOAPIC_DEBUG ON) set(IRQ_DEBUG ON) set(INTERRUPT_DEBUG ON)