/* * Copyright (c) 2021, 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 #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_INDIRECT_DESC ((u64)1 << 28) #define VIRTIO_F_VERSION_1 ((u64)1 << 32) #define VIRTIO_F_RING_PACKED ((u64)1 << 34) #define VIRTIO_F_IN_ORDER ((u64)1 << 35) #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 enum class ConfigurationType : u8 { Common = 1, Notify = 2, ISR = 3, Device = 4, PCI = 5 }; struct Configuration { ConfigurationType cfg_type; u8 bar; u32 offset; u32 length; }; class VirtIO { public: static void detect(); }; class VirtIODevice : public PCI::Device { public: VirtIODevice(PCI::Address, String); virtual ~VirtIODevice() override; protected: const String 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; } }; const Configuration* get_config(ConfigurationType cfg_type, u32 index = 0) const { for (auto& cfg : m_configs) { if (cfg.cfg_type != cfg_type) continue; if (index > 0) { index--; continue; } return &cfg; } return nullptr; } 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 setup_queues(u16 requested_queue_count = 0); void finish_init(); VirtIOQueue& get_queue(u16 queue_index) { VERIFY(queue_index < m_queue_count); return m_queues[queue_index]; } 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 ScatterGatherList&, BufferType, void* token); virtual bool handle_device_config_change() = 0; virtual void handle_queue_update(u16 queue_index) = 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_queue(u16 queue_index); bool activate_queue(u16 queue_index); void notify_queue(u16 queue_index); void reset_device(); u8 isr_status(); virtual void handle_irq(const RegisterState&) override; NonnullOwnPtrVector m_queues; NonnullOwnPtrVector 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 }; bool m_did_setup_queues { false }; u32 m_notify_multiplier { 0 }; }; }