summaryrefslogtreecommitdiff
path: root/Kernel/VirtIO/VirtIO.h
blob: c6d5faa320a8fda653ec05d56cf419a5d6ac4928 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/*
 * Copyright (c) 2021, the SerenityOS developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/NonnullOwnPtrVector.h>
#include <Kernel/Bus/PCI/Access.h>
#include <Kernel/Bus/PCI/Device.h>
#include <Kernel/IO.h>
#include <Kernel/Interrupts/IRQHandler.h>
#include <Kernel/Memory/MemoryManager.h>
#include <Kernel/VirtIO/VirtIOQueue.h>

namespace Kernel {

#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<Region> base;
        size_t size { 0 };

        template<typename T>
        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<typename T>
        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<typename F>
    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 mask_status_bits(u8 status_mask);
    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];
    }

    const VirtIOQueue& get_queue(u16 queue_index) const
    {
        VERIFY(queue_index < m_queue_count);
        return m_queues[queue_index];
    }

    template<typename F>
    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_chain_and_notify(u16 queue_index, VirtIOQueueChain& chain);

    virtual bool handle_device_config_change() = 0;
    virtual void handle_queue_update(u16 queue_index) = 0;

private:
    template<typename T>
    void out(u16 address, T value)
    {
        m_io_base.offset(address).out(value);
    }

    template<typename T>
    T in(u16 address)
    {
        return m_io_base.offset(address).in<T>();
    }

    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 bool handle_irq(const RegisterState&) override;

    NonnullOwnPtrVector<VirtIOQueue> m_queues;
    NonnullOwnPtrVector<Configuration> 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 };
};

}