Skip to content

Commit 658e64e

Browse files
committed
drivers: Introduce vhost driver subsystem
This introduces the vhost driver framework, providing standard APIs for VIRTIO backend implementations in Zephyr. Includes vringh utility for host-side VIRTIO ring processing based on Linux kernel implementation. Signed-off-by: TOKITA Hiroshi <[email protected]>
1 parent b810253 commit 658e64e

File tree

7 files changed

+810
-0
lines changed

7 files changed

+810
-0
lines changed

drivers/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ add_subdirectory_ifdef(CONFIG_STEPPER stepper)
9191
add_subdirectory_ifdef(CONFIG_SYSCON syscon)
9292
add_subdirectory_ifdef(CONFIG_SYS_CLOCK_EXISTS timer)
9393
add_subdirectory_ifdef(CONFIG_TEE tee)
94+
add_subdirectory_ifdef(CONFIG_VHOST vhost)
9495
add_subdirectory_ifdef(CONFIG_VIDEO video)
9596
add_subdirectory_ifdef(CONFIG_VIRTIO virtio)
9697
add_subdirectory_ifdef(CONFIG_VIRTUALIZATION virtualization)

drivers/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ source "drivers/syscon/Kconfig"
9090
source "drivers/timer/Kconfig"
9191
source "drivers/usb/Kconfig"
9292
source "drivers/usb_c/Kconfig"
93+
source "drivers/vhost/Kconfig"
9394
source "drivers/video/Kconfig"
9495
source "drivers/virtio/Kconfig"
9596
source "drivers/virtualization/Kconfig"

drivers/vhost/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (c) 2025 TOKITA Hiroshi
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
zephyr_library()
5+
6+
zephyr_library_sources_ifdef(CONFIG_VHOST vringh.c)

drivers/vhost/Kconfig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) 2025 TOKITA Hiroshi
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config VHOST
5+
bool "support for VIRTIO"
6+
help
7+
Enable options for VIRTIO
8+
9+
if VHOST
10+
11+
endif # VIRTIO
12+
13+
module = VHOST
14+
module-str = VHOST
15+
source "subsys/logging/Kconfig.template.log_config"

drivers/vhost/vringh.c

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
/*
2+
* Copyright (c) 2025 TOKITA Hiroshi
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/drivers/vhost/vringh.h>
8+
#include <zephyr/drivers/virtio/virtio_config.h>
9+
#include <zephyr/sys/byteorder.h>
10+
#include <zephyr/logging/log.h>
11+
#include <zephyr/spinlock.h>
12+
#include <string.h>
13+
14+
LOG_MODULE_REGISTER(vhost_vringh);
15+
16+
int vringh_init(struct vringh *vrh, uint64_t features, uint16_t num, bool weak_barriers,
17+
struct virtq_desc *desc, struct virtq_avail *avail, struct virtq_used *used)
18+
{
19+
if (!vrh || !desc || !avail || !used) {
20+
return -EINVAL;
21+
}
22+
23+
memset(vrh, 0, sizeof(*vrh));
24+
25+
vrh->event_indices = !!(features & BIT(VIRTIO_RING_F_EVENT_IDX));
26+
vrh->weak_barriers = weak_barriers;
27+
vrh->last_avail_idx = 0;
28+
vrh->last_used_idx = 0;
29+
vrh->completed = 0;
30+
31+
vrh->vring.num = num;
32+
vrh->vring.desc = desc;
33+
vrh->vring.avail = avail;
34+
vrh->vring.used = used;
35+
36+
return 0;
37+
}
38+
39+
static void vringh_callback(const struct device *dev, uint16_t queue_id, void *ptr)
40+
{
41+
struct vringh *vrh = ptr;
42+
43+
if (vrh->received) {
44+
vrh->received(vrh);
45+
}
46+
}
47+
48+
int vringh_init_device(struct vringh *vrh, const struct device *dev, uint16_t queue_id,
49+
void (*notify_callback)(struct vringh *vrh))
50+
{
51+
uint64_t drv_feats;
52+
void *parts[3];
53+
size_t q_num;
54+
int ret;
55+
56+
if (!vrh || !dev) {
57+
return -EINVAL;
58+
}
59+
60+
ret = vhost_get_virtq(dev, queue_id, parts, &q_num);
61+
if (ret < 0) {
62+
LOG_ERR("vhost_get_virtq failed: %d", ret);
63+
return ret;
64+
}
65+
66+
ret = vhost_get_driver_features(dev, &drv_feats);
67+
if (ret < 0) {
68+
LOG_ERR("vhost_get_driver_features failed: %d", ret);
69+
return ret;
70+
}
71+
72+
ret = vringh_init(vrh, drv_feats, q_num, false, parts[0], parts[1], parts[2]);
73+
if (ret < 0) {
74+
LOG_ERR("vringh_init failed: %d", ret);
75+
return ret;
76+
}
77+
78+
vrh->dev = dev;
79+
vrh->queue_id = queue_id;
80+
vrh->received = notify_callback;
81+
82+
ret = vhost_set_notify_callback(dev, queue_id, vringh_callback, (void *)vrh);
83+
if (ret < 0) {
84+
LOG_ERR("vhost_set_notify_callback failed: %d", ret);
85+
return ret;
86+
}
87+
88+
return 0;
89+
}
90+
91+
int vringh_getdesc(struct vringh *vrh, struct vringh_iov *riov, struct vringh_iov *wiov,
92+
uint16_t *head_out)
93+
{
94+
if (!vrh || !riov || !wiov || !head_out) {
95+
return -EINVAL;
96+
}
97+
98+
k_spinlock_key_t key = k_spin_lock(&vrh->lock);
99+
struct vring *vr = &vrh->vring;
100+
const uint16_t avail_idx = sys_le16_to_cpu(vrh->vring.avail->idx);
101+
102+
const uint16_t slot = vrh->last_avail_idx % vr->num;
103+
const uint16_t head = sys_le16_to_cpu(vr->avail->ring[slot]);
104+
uint16_t idx = head;
105+
size_t count = 0;
106+
uint16_t flags;
107+
108+
if (vrh->last_avail_idx == avail_idx) {
109+
k_spin_unlock(&vrh->lock, key);
110+
return 0;
111+
}
112+
113+
if (head >= vrh->vring.num) {
114+
k_spin_unlock(&vrh->lock, key);
115+
LOG_ERR("Invalid descriptor head: %u >= %u", head, vrh->vring.num);
116+
return -EINVAL;
117+
}
118+
119+
flags = sys_le16_to_cpu(vr->used->flags);
120+
flags |= VIRTQ_USED_F_NO_NOTIFY;
121+
vr->used->flags = sys_cpu_to_le16(flags);
122+
123+
barrier_dmem_fence_full();
124+
125+
k_spin_unlock(&vrh->lock, key);
126+
127+
vringh_iov_reset(riov);
128+
vringh_iov_reset(wiov);
129+
130+
do {
131+
const struct virtq_desc *d = &vr->desc[idx];
132+
const uint64_t gpa = sys_le64_to_cpu(d->addr);
133+
const uint32_t len = sys_le32_to_cpu(d->len);
134+
const uint16_t next = sys_le16_to_cpu(d->next);
135+
136+
flags = sys_le16_to_cpu(d->flags);
137+
138+
if (count >= vr->num) {
139+
LOG_ERR("Descriptor chain too long: %zu", count);
140+
return -ENOMEM;
141+
}
142+
count++;
143+
144+
if (len == 0) {
145+
LOG_WRN("Zero-length descriptor at index %u", idx);
146+
continue;
147+
}
148+
149+
struct vringh_iov *iov = (flags & VIRTQ_DESC_F_WRITE) ? wiov : riov;
150+
151+
if (iov->used >= iov->max_num) {
152+
LOG_ERR("IOV buffer full: %u >= %u", iov->used, iov->max_num);
153+
return -E2BIG;
154+
}
155+
156+
size_t filled;
157+
int ret =
158+
vhost_prepare_iovec(vrh->dev, vrh->queue_id, gpa, len, &iov->iov[iov->used],
159+
iov->max_num - iov->used, &filled);
160+
161+
if (ret < 0) {
162+
LOG_ERR("vhost_prepare_iovec failed: %d", ret);
163+
int rc = vhost_release_iovec(vrh->dev, vrh->queue_id);
164+
165+
if (rc < 0) {
166+
LOG_ERR("vhost_release_iovec failed: %d", rc);
167+
vhost_set_device_status(vrh->dev, DEVICE_STATUS_FAILED);
168+
}
169+
vringh_iov_reset(riov);
170+
vringh_iov_reset(wiov);
171+
172+
return ret;
173+
}
174+
175+
iov->used += filled;
176+
177+
idx = next;
178+
} while (flags & VIRTQ_DESC_F_NEXT);
179+
180+
*head_out = head;
181+
182+
key = k_spin_lock(&vrh->lock);
183+
vrh->last_avail_idx++;
184+
k_spin_unlock(&vrh->lock, key);
185+
186+
return 1;
187+
}
188+
189+
int vringh_complete(struct vringh *vrh, uint16_t head, uint32_t total_len)
190+
{
191+
struct vring *vr = &vrh->vring;
192+
int rc = 0;
193+
194+
if (!vrh) {
195+
return -EINVAL;
196+
}
197+
198+
rc = vhost_release_iovec(vrh->dev, vrh->queue_id);
199+
if (rc < 0) {
200+
LOG_ERR("vhost_release_iovec failed: %d", rc);
201+
vhost_set_device_status(vrh->dev, DEVICE_STATUS_FAILED);
202+
return rc;
203+
}
204+
205+
k_spinlock_key_t key = k_spin_lock(&vrh->lock);
206+
207+
const uint16_t used_idx = sys_le16_to_cpu(vr->used->idx);
208+
struct virtq_used_elem *ue = &vr->used->ring[used_idx % vr->num];
209+
210+
LOG_DBG("used_idx %u ue={%u, %u}", used_idx, head, total_len);
211+
212+
ue->id = sys_cpu_to_le32((uint32_t)head);
213+
ue->len = sys_cpu_to_le32(total_len);
214+
215+
barrier_dmem_fence_full();
216+
vr->used->idx = sys_cpu_to_le16(used_idx + 1);
217+
vrh->last_used_idx++;
218+
219+
/* Clear no-notify flag and ensure proper notification behavior */
220+
uint16_t flags = sys_le16_to_cpu(vr->used->flags);
221+
222+
flags &= ~VIRTQ_USED_F_NO_NOTIFY;
223+
vr->used->flags = sys_cpu_to_le16(flags);
224+
barrier_dmem_fence_full();
225+
226+
k_spin_unlock(&vrh->lock, key);
227+
228+
return rc;
229+
}
230+
231+
int vringh_abandon(struct vringh *vrh, uint32_t num)
232+
{
233+
struct vring *vr = &vrh->vring;
234+
int rc = 0;
235+
236+
if (num == 0) {
237+
return 0;
238+
}
239+
240+
if (!vrh) {
241+
return -EINVAL;
242+
}
243+
244+
rc = vhost_release_iovec(vrh->dev, vrh->queue_id);
245+
if (rc < 0) {
246+
LOG_ERR("vhost_release_iovec failed: %d", rc);
247+
vhost_set_device_status(vrh->dev, DEVICE_STATUS_FAILED);
248+
}
249+
250+
k_spinlock_key_t key = k_spin_lock(&vrh->lock);
251+
252+
if (num > vrh->last_avail_idx) {
253+
LOG_ERR("Cannot abandon %u descs, avail=%u", num, vrh->last_avail_idx);
254+
rc = -ERANGE;
255+
} else {
256+
vrh->last_avail_idx -= num;
257+
LOG_DBG("Abandoned %u descs, new last_avail_idx: %u", num, vrh->last_avail_idx);
258+
}
259+
260+
uint16_t flags = sys_le16_to_cpu(vr->used->flags);
261+
262+
flags &= ~VIRTQ_USED_F_NO_NOTIFY;
263+
vr->used->flags = sys_cpu_to_le16(flags);
264+
265+
barrier_dmem_fence_full();
266+
267+
k_spin_unlock(&vrh->lock, key);
268+
269+
return rc;
270+
}
271+
272+
void vringh_iov_reset(struct vringh_iov *iov)
273+
{
274+
if (!iov || !iov->iov) {
275+
return;
276+
}
277+
278+
if (iov->consumed > 0 && iov->i < iov->used) {
279+
iov->iov[iov->i].iov_len += iov->consumed;
280+
iov->iov[iov->i].iov_base = (char *)iov->iov[iov->i].iov_base - iov->consumed;
281+
}
282+
283+
iov->consumed = 0;
284+
iov->i = 0;
285+
iov->used = 0;
286+
}
287+
288+
int vringh_need_notify(struct vringh *vrh)
289+
{
290+
if (!vrh) {
291+
return -EINVAL;
292+
}
293+
294+
k_spinlock_key_t key = k_spin_lock(&vrh->lock);
295+
uint16_t flags = sys_le16_to_cpu(vrh->vring.used->flags);
296+
297+
k_spin_unlock(&vrh->lock, key);
298+
299+
return !(flags & VIRTQ_USED_F_NO_NOTIFY);
300+
}
301+
302+
void vringh_notify(struct vringh *vrh)
303+
{
304+
k_spinlock_key_t key = k_spin_lock(&vrh->lock);
305+
const uint16_t flags = sys_le16_to_cpu(vrh->vring.avail->flags);
306+
307+
k_spin_unlock(&vrh->lock, key);
308+
309+
if (flags & VIRTQ_AVAIL_F_NO_INTERRUPT) {
310+
return;
311+
}
312+
313+
vhost_queue_notify(vrh->dev, vrh->queue_id);
314+
}

0 commit comments

Comments
 (0)