Skip to content

Commit e752972

Browse files
committed
drivers: virtio: add VIRTIO MMIO transport driver
Add virtio-mmio driver Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
1 parent ef7357d commit e752972

File tree

4 files changed

+366
-0
lines changed

4 files changed

+366
-0
lines changed

drivers/virtio/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ zephyr_library()
55

66
zephyr_library_sources_ifdef(CONFIG_VIRTIO virtqueue.c virtio_common.c)
77
zephyr_library_sources_ifdef(CONFIG_VIRTIO_PCI virtio_pci.c)
8+
zephyr_library_sources_ifdef(CONFIG_VIRTIO_MMIO virtio_mmio.c)

drivers/virtio/Kconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ config VIRTIO_PCI
1515
help
1616
Enable options for VIRTIO over PCI
1717

18+
config VIRTIO_MMIO
19+
bool "support for VIRTIO over MMIO"
20+
default y
21+
depends on DT_HAS_VIRTIO_MMIO_ENABLED
22+
help
23+
Enable options for VIRTIO over MMIO
24+
1825
endif # VIRTIO
1926

2027
module = VIRTIO

drivers/virtio/virtio_mmio.c

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
/*
2+
* Copyright (c) 2025 TOKITA Hiroshi
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/device.h>
8+
#include <zephyr/kernel/mm.h>
9+
#include <zephyr/logging/log.h>
10+
#include <zephyr/spinlock.h>
11+
#include <zephyr/sys/barrier.h>
12+
#include <zephyr/sys/byteorder.h>
13+
#include <zephyr/virtio/virtio.h>
14+
#include <zephyr/virtio/virtqueue.h>
15+
#include "virtio_common.h"
16+
17+
#define DT_DRV_COMPAT virtio_mmio
18+
19+
LOG_MODULE_REGISTER(virtio_mmio, CONFIG_VIRTIO_LOG_LEVEL);
20+
21+
/*
22+
* Based on Virtual I/O Device (VIRTIO) Version 1.3 specification:
23+
* https://docs.oasis-open.org/virtio/virtio/v1.3/csd01/virtio-v1.3-csd01.pdf
24+
*/
25+
26+
#define VIRTIO_MMIO_MAGIC_VALUE 0x000
27+
#define VIRTIO_MMIO_VERSION 0x004
28+
#define VIRTIO_MMIO_DEVICE_ID 0x008
29+
#define VIRTIO_MMIO_VENDOR_ID 0x00c
30+
#define VIRTIO_MMIO_DEVICE_FEATURES 0x010
31+
#define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014
32+
#define VIRTIO_MMIO_DRIVER_FEATURES 0x020
33+
#define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024
34+
#define VIRTIO_MMIO_QUEUE_SEL 0x030
35+
#define VIRTIO_MMIO_QUEUE_SIZE_MAX 0x034
36+
#define VIRTIO_MMIO_QUEUE_SIZE 0x038
37+
#define VIRTIO_MMIO_QUEUE_READY 0x044
38+
#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050
39+
#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060
40+
#define VIRTIO_MMIO_INTERRUPT_ACK 0x064
41+
#define VIRTIO_MMIO_STATUS 0x070
42+
#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080
43+
#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084
44+
#define VIRTIO_MMIO_QUEUE_DRIVER_LOW 0x090
45+
#define VIRTIO_MMIO_QUEUE_DRIVER_HIGH 0x094
46+
#define VIRTIO_MMIO_QUEUE_DEVICE_LOW 0x0a0
47+
#define VIRTIO_MMIO_QUEUE_DEVICE_HIGH 0x0a4
48+
#define VIRTIO_MMIO_QUEUE_SHM_SEL 0x0ac
49+
#define VIRTIO_MMIO_QUEUE_SHM_LEN_LOW 0x0b0
50+
#define VIRTIO_MMIO_QUEUE_SHM_LEN_HIGH 0x0b4
51+
#define VIRTIO_MMIO_QUEUE_SHM_BASE_LOW 0x0b8
52+
#define VIRTIO_MMIO_QUEUE_SHM_BASE_HIGH 0x0bc
53+
#define VIRTIO_MMIO_QUEUE_RESET 0x0c0
54+
#define VIRTIO_MMIO_QUEUE_CONFIG_GENERATION 0x0fc
55+
#define VIRTIO_MMIO_CONFIG 0x100
56+
57+
#define VIRTIO_MMIO_MAGIC 0x74726976
58+
#define VIRTIO_MMIO_SUPPORTED_VERSION 2
59+
#define VIRTIO_MMIO_INVALID_DEVICE_ID 0
60+
61+
#define DEV_CFG(dev) ((const struct virtio_mmio_config *)(dev)->config)
62+
#define DEV_DATA(dev) ((struct virtio_mmio_data *)(dev)->data)
63+
64+
struct virtio_mmio_data {
65+
DEVICE_MMIO_NAMED_RAM(reg_base);
66+
67+
struct virtq *virtqueues;
68+
uint16_t virtqueue_count;
69+
70+
struct k_spinlock isr_lock;
71+
struct k_spinlock notify_lock;
72+
};
73+
74+
struct virtio_mmio_config {
75+
DEVICE_MMIO_NAMED_ROM(reg_base);
76+
};
77+
78+
static inline uint32_t virtio_mmio_read32(const struct device *dev, uint32_t offset)
79+
{
80+
const mem_addr_t reg = DEVICE_MMIO_NAMED_GET(dev, reg_base) + offset;
81+
uint32_t val;
82+
83+
barrier_dmem_fence_full();
84+
val = sys_read32(reg);
85+
return sys_le32_to_cpu(val);
86+
}
87+
88+
static inline void virtio_mmio_write32(const struct device *dev, uint32_t offset, uint32_t val)
89+
{
90+
const mem_addr_t reg = DEVICE_MMIO_NAMED_GET(dev, reg_base) + offset;
91+
92+
sys_write32(sys_cpu_to_le32(val), reg);
93+
barrier_dmem_fence_full();
94+
}
95+
96+
static void virtio_mmio_isr(const struct device *dev)
97+
{
98+
struct virtio_mmio_data *data = dev->data;
99+
k_spinlock_key_t key = k_spin_lock(&data->isr_lock);
100+
const uint32_t isr_status = virtio_mmio_read32(dev, VIRTIO_MMIO_INTERRUPT_STATUS);
101+
102+
virtio_mmio_write32(dev, VIRTIO_MMIO_INTERRUPT_ACK, isr_status);
103+
virtio_isr(dev, isr_status, data->virtqueue_count);
104+
105+
k_spin_unlock(&data->isr_lock, key);
106+
}
107+
108+
struct virtq *virtio_mmio_get_virtqueue(const struct device *dev, uint16_t queue_idx)
109+
{
110+
struct virtio_mmio_data *data = dev->data;
111+
112+
return queue_idx < data->virtqueue_count ? &data->virtqueues[queue_idx] : NULL;
113+
}
114+
115+
static void virtio_mmio_notify_queue(const struct device *dev, uint16_t queue_idx)
116+
{
117+
struct virtio_mmio_data *data = dev->data;
118+
119+
k_spinlock_key_t key = k_spin_lock(&data->notify_lock);
120+
121+
virtio_mmio_write32(dev, VIRTIO_MMIO_QUEUE_NOTIFY, queue_idx);
122+
123+
k_spin_unlock(&data->notify_lock, key);
124+
}
125+
126+
static void *virtio_mmio_get_device_specific_config(const struct device *dev)
127+
{
128+
const mem_addr_t reg = DEVICE_MMIO_NAMED_GET(dev, reg_base) + VIRTIO_MMIO_CONFIG;
129+
130+
barrier_dmem_fence_full();
131+
132+
return (void *)reg;
133+
}
134+
135+
static bool virtio_mmio_read_device_feature_bit(const struct device *dev, int bit)
136+
{
137+
virtio_mmio_write32(dev, VIRTIO_MMIO_DEVICE_FEATURES_SEL, bit / 32);
138+
const uint32_t val = virtio_mmio_read32(dev, VIRTIO_MMIO_DEVICE_FEATURES);
139+
140+
return !!(val & BIT(bit % 32));
141+
}
142+
143+
static void virtio_mmio_write_driver_feature_bit(const struct device *dev, int bit, bool value)
144+
{
145+
const uint32_t mask = sys_cpu_to_le32(BIT(bit % 32));
146+
147+
virtio_mmio_write32(dev, VIRTIO_MMIO_DRIVER_FEATURES_SEL, bit / 32);
148+
149+
const uint32_t regval = virtio_mmio_read32(dev, VIRTIO_MMIO_DRIVER_FEATURES);
150+
151+
if (value) {
152+
virtio_mmio_write32(dev, VIRTIO_MMIO_DRIVER_FEATURES, regval | mask);
153+
} else {
154+
virtio_mmio_write32(dev, VIRTIO_MMIO_DRIVER_FEATURES, regval & ~mask);
155+
}
156+
}
157+
158+
static bool virtio_mmio_read_status_bit(const struct device *dev, int bit)
159+
{
160+
const uint32_t val = virtio_mmio_read32(dev, VIRTIO_MMIO_STATUS);
161+
162+
return !!(val & BIT(bit));
163+
}
164+
165+
static void virtio_mmio_write_status_bit(const struct device *dev, int bit)
166+
{
167+
const uint32_t mask = sys_cpu_to_le32(BIT(bit));
168+
const uint32_t val = virtio_mmio_read32(dev, VIRTIO_MMIO_STATUS);
169+
170+
virtio_mmio_write32(dev, VIRTIO_MMIO_STATUS, val | mask);
171+
}
172+
173+
static int virtio_mmio_write_driver_feature_bit_range_check(const struct device *dev, int bit,
174+
bool value)
175+
{
176+
if (!IN_RANGE(bit, DEV_TYPE_FEAT_RANGE_0_BEGIN, DEV_TYPE_FEAT_RANGE_0_END) ||
177+
!IN_RANGE(bit, DEV_TYPE_FEAT_RANGE_1_BEGIN, DEV_TYPE_FEAT_RANGE_1_END)) {
178+
return -EINVAL;
179+
}
180+
181+
virtio_mmio_write_driver_feature_bit(dev, bit, value);
182+
183+
return 0;
184+
}
185+
186+
static int virtio_mmio_commit_feature_bits(const struct device *dev)
187+
{
188+
virtio_mmio_write_status_bit(dev, DEVICE_STATUS_FEATURES_OK);
189+
if (!virtio_mmio_read_status_bit(dev, DEVICE_STATUS_FEATURES_OK)) {
190+
return -EINVAL;
191+
}
192+
193+
return 0;
194+
}
195+
196+
static void virtio_mmio_reset(const struct device *dev)
197+
{
198+
virtio_mmio_write32(dev, VIRTIO_MMIO_STATUS, 0);
199+
200+
while (virtio_mmio_read_status_bit(dev, VIRTIO_MMIO_STATUS) != 0) {
201+
}
202+
}
203+
204+
static int virtio_mmio_set_virtqueue(const struct device *dev, uint16_t virtqueue_n,
205+
struct virtq *virtqueue)
206+
{
207+
virtio_mmio_write32(dev, VIRTIO_MMIO_QUEUE_SEL, virtqueue_n);
208+
209+
uint16_t max_queue_size = virtio_mmio_read32(dev, VIRTIO_MMIO_QUEUE_SIZE_MAX);
210+
211+
if (max_queue_size < virtqueue->num) {
212+
LOG_ERR("%s doesn't support queue %d bigger than %d, tried to set "
213+
"one with size %d",
214+
dev->name, virtqueue_n, max_queue_size, virtqueue->num);
215+
return -EINVAL;
216+
}
217+
218+
virtio_mmio_write32(dev, VIRTIO_MMIO_QUEUE_SIZE, virtqueue->num);
219+
virtio_mmio_write32(dev, VIRTIO_MMIO_QUEUE_DESC_LOW,
220+
k_mem_phys_addr(virtqueue->desc) & UINT32_MAX);
221+
virtio_mmio_write32(dev, VIRTIO_MMIO_QUEUE_DESC_HIGH,
222+
k_mem_phys_addr(virtqueue->desc) >> 32);
223+
virtio_mmio_write32(dev, VIRTIO_MMIO_QUEUE_DRIVER_LOW,
224+
k_mem_phys_addr(virtqueue->avail) & UINT32_MAX);
225+
virtio_mmio_write32(dev, VIRTIO_MMIO_QUEUE_DRIVER_HIGH,
226+
k_mem_phys_addr(virtqueue->avail) >> 32);
227+
virtio_mmio_write32(dev, VIRTIO_MMIO_QUEUE_DEVICE_LOW,
228+
k_mem_phys_addr(virtqueue->used) & UINT32_MAX);
229+
virtio_mmio_write32(dev, VIRTIO_MMIO_QUEUE_DEVICE_HIGH,
230+
k_mem_phys_addr(virtqueue->used) >> 32);
231+
232+
virtio_mmio_write32(dev, VIRTIO_MMIO_QUEUE_READY, 1);
233+
234+
return 0;
235+
}
236+
237+
static int virtio_mmio_set_virtqueues(const struct device *dev, uint16_t queue_count,
238+
virtio_enumerate_queues cb, void *opaque)
239+
{
240+
struct virtio_mmio_data *data = dev->data;
241+
242+
data->virtqueues = k_malloc(queue_count * sizeof(struct virtq));
243+
if (!data->virtqueues) {
244+
LOG_ERR("failed to allocate virtqueue array");
245+
return -ENOMEM;
246+
}
247+
data->virtqueue_count = queue_count;
248+
249+
for (int i = 0; i < queue_count; i++) {
250+
virtio_mmio_write32(dev, VIRTIO_MMIO_QUEUE_SEL, i);
251+
252+
const uint16_t queue_size =
253+
cb(i, virtio_mmio_read32(dev, VIRTIO_MMIO_QUEUE_SIZE_MAX), opaque);
254+
255+
int ret = virtq_create(&data->virtqueues[i], queue_size);
256+
257+
if (ret != 0) {
258+
for (int j = 0; j < i; i++) {
259+
virtq_free(&data->virtqueues[j]);
260+
}
261+
return ret;
262+
}
263+
ret = virtio_mmio_set_virtqueue(dev, i, &data->virtqueues[i]);
264+
if (ret != 0) {
265+
for (int j = 0; j < i; i++) {
266+
virtq_free(&data->virtqueues[j]);
267+
}
268+
return ret;
269+
}
270+
}
271+
272+
return 0;
273+
}
274+
275+
static int virtio_mmio_init_virtqueues(const struct device *dev, uint16_t num_queues,
276+
virtio_enumerate_queues cb, void *opaque)
277+
{
278+
return virtio_mmio_set_virtqueues(dev, num_queues, cb, opaque);
279+
}
280+
281+
static void virtio_mmio_finalize_init(const struct device *dev)
282+
{
283+
virtio_mmio_write_status_bit(dev, DEVICE_STATUS_DRIVER_OK);
284+
}
285+
286+
static const struct virtio_driver_api virtio_mmio_driver_api = {
287+
.get_virtqueue = virtio_mmio_get_virtqueue,
288+
.notify_virtqueue = virtio_mmio_notify_queue,
289+
.get_device_specific_config = virtio_mmio_get_device_specific_config,
290+
.read_device_feature_bit = virtio_mmio_read_device_feature_bit,
291+
.write_driver_feature_bit = virtio_mmio_write_driver_feature_bit_range_check,
292+
.commit_feature_bits = virtio_mmio_commit_feature_bits,
293+
.init_virtqueues = virtio_mmio_init_virtqueues,
294+
.finalize_init = virtio_mmio_finalize_init,
295+
};
296+
297+
static int virtio_mmio_init_common(const struct device *dev)
298+
{
299+
DEVICE_MMIO_NAMED_MAP(dev, reg_base, K_MEM_CACHE_NONE);
300+
301+
const uint32_t magic = virtio_mmio_read32(dev, VIRTIO_MMIO_MAGIC_VALUE);
302+
303+
if (magic != VIRTIO_MMIO_MAGIC) {
304+
LOG_ERR("Invalid magic value %x", magic);
305+
return -EINVAL;
306+
}
307+
308+
const uint32_t version = virtio_mmio_read32(dev, VIRTIO_MMIO_VERSION);
309+
310+
if (version != VIRTIO_MMIO_SUPPORTED_VERSION) {
311+
LOG_ERR("Invalid version %x", version);
312+
return -EINVAL;
313+
}
314+
315+
const uint32_t dev_id = virtio_mmio_read32(dev, VIRTIO_MMIO_DEVICE_ID);
316+
317+
if (dev_id == VIRTIO_MMIO_INVALID_DEVICE_ID) {
318+
LOG_ERR("Invalid device id %x", dev_id);
319+
return -EINVAL;
320+
}
321+
322+
LOG_DBG("VENDOR_ID = %x", virtio_mmio_read32(dev, VIRTIO_MMIO_VENDOR_ID));
323+
324+
virtio_mmio_reset(dev);
325+
326+
virtio_mmio_write_status_bit(dev, DEVICE_STATUS_ACKNOWLEDGE);
327+
virtio_mmio_write_status_bit(dev, DEVICE_STATUS_DRIVER);
328+
329+
virtio_mmio_write_driver_feature_bit(dev, VIRTIO_F_VERSION_1, true);
330+
331+
return 0;
332+
};
333+
334+
#define VIRTIO_MMIO_DEFINE(inst) \
335+
static struct virtio_mmio_data virtio_mmio_data##inst; \
336+
static struct virtio_mmio_config virtio_mmio_config##inst = { \
337+
DEVICE_MMIO_NAMED_ROM_INIT(reg_base, DT_DRV_INST(inst)), \
338+
}; \
339+
static int virtio_mmio_init##inst(const struct device *dev) \
340+
{ \
341+
IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), virtio_mmio_isr, \
342+
DEVICE_DT_INST_GET(inst), 0); \
343+
int ret = virtio_mmio_init_common(dev); \
344+
irq_enable(DT_INST_IRQN(inst)); \
345+
return ret; \
346+
} \
347+
DEVICE_DT_INST_DEFINE(inst, virtio_mmio_init##inst, NULL, &virtio_mmio_data##inst, \
348+
&virtio_mmio_config##inst, POST_KERNEL, 0, &virtio_mmio_driver_api);
349+
350+
DT_INST_FOREACH_STATUS_OKAY(VIRTIO_MMIO_DEFINE)

dts/bindings/virtio/virtio,mmio.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) 2025 TOKITA Hiroshi
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: VIRTIO over MMIO
5+
6+
compatible: "virtio,mmio"
7+
8+
include: [base.yaml]

0 commit comments

Comments
 (0)