Skip to content

Commit e0477f9

Browse files
committed
Implement Virtio-net Device Operations and Initialization
- Defined struct `virtio_net_dev` to represent the virtio-net device. - Implemented `virtio_net_init` to initialize the TAP device and prepare it for communication. - Added `virtio_net_init_pci` for registering the virtio-net device to PCI, setting up virtqueues. - Implemented functions to enable and handle available events for RX and TX virtqueues. - Added functions to notify the guest OS about used descriptors after packet processing.
1 parent ffe224e commit e0477f9

File tree

1 file changed

+285
-0
lines changed

1 file changed

+285
-0
lines changed

src/virtio-net.c

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
#include <errno.h>
2+
#include <fcntl.h>
3+
#include <linux/if.h>
4+
#include <linux/if_tun.h>
5+
#include <poll.h>
6+
#include <pthread.h>
7+
#include <signal.h>
8+
#include <stddef.h>
9+
#include <stdio.h>
10+
#include <stdlib.h>
11+
#include <string.h>
12+
#include <sys/eventfd.h>
13+
#include <sys/ioctl.h>
14+
#include <sys/uio.h>
15+
#include <unistd.h>
16+
17+
#include "err.h"
18+
#include "utils.h"
19+
#include "virtio-net.h"
20+
#include "vm.h"
21+
22+
#define TAP_INTERFACE "tap%d"
23+
#define VIRTQ_RX 0
24+
#define VIRTQ_TX 1
25+
#define NOTIFY_OFFSET 2
26+
27+
static volatile bool thread_stop = false;
28+
29+
static int virtio_net_virtq_available_rx(struct virtio_net_dev *dev,
30+
int timeout)
31+
{
32+
struct pollfd pollfd = (struct pollfd){
33+
.fd = dev->tapfd,
34+
.events = POLLIN,
35+
};
36+
return (poll(&pollfd, 1, timeout) > 0) && (pollfd.revents & POLLIN);
37+
}
38+
39+
static int virtio_net_virtq_available_tx(struct virtio_net_dev *dev,
40+
int timeout)
41+
{
42+
struct pollfd pollfds[] = {
43+
[0] = {.fd = dev->tx_ioeventfd, .events = POLLIN},
44+
[1] = {.fd = dev->tapfd, .events = POLLOUT},
45+
};
46+
47+
int ret = poll(pollfds, 2, timeout);
48+
49+
return ret > 0 && (pollfds[0].revents & POLLIN) &&
50+
(pollfds[1].revents & POLLOUT);
51+
}
52+
53+
static void *virtio_net_vq_avail_handler_rx(void *arg)
54+
{
55+
struct virtq *vq = (struct virtq *) arg;
56+
struct virtio_net_dev *dev = (struct virtio_net_dev *) vq->dev;
57+
58+
while (!__atomic_load_n(&thread_stop, __ATOMIC_RELAXED)) {
59+
vq->guest_event->flags = VRING_PACKED_EVENT_FLAG_ENABLE;
60+
if (virtio_net_virtq_available_rx(dev, -1))
61+
virtq_handle_avail(vq);
62+
}
63+
return NULL;
64+
}
65+
66+
static void *virtio_net_vq_avail_handler_tx(void *arg)
67+
{
68+
struct virtq *vq = (struct virtq *) arg;
69+
struct virtio_net_dev *dev = (struct virtio_net_dev *) vq->dev;
70+
71+
while (!__atomic_load_n(&thread_stop, __ATOMIC_RELAXED)) {
72+
vq->guest_event->flags = VRING_PACKED_EVENT_FLAG_ENABLE;
73+
if (virtio_net_virtq_available_tx(dev, -1))
74+
virtq_handle_avail(vq);
75+
}
76+
return NULL;
77+
}
78+
79+
static void virtio_net_enable_vq_rx(struct virtq *vq)
80+
{
81+
struct virtio_net_dev *dev = (struct virtio_net_dev *) vq->dev;
82+
vm_t *v = container_of(dev, vm_t, virtio_net_dev);
83+
84+
if (vq->info.enable)
85+
return;
86+
vq->info.enable = true;
87+
vq->desc_ring =
88+
(struct vring_packed_desc *) vm_guest_to_host(v, vq->info.desc_addr);
89+
vq->device_event = (struct vring_packed_desc_event *) vm_guest_to_host(
90+
v, vq->info.device_addr);
91+
vq->guest_event = (struct vring_packed_desc_event *) vm_guest_to_host(
92+
v, vq->info.driver_addr);
93+
uint64_t addr = virtio_pci_get_notify_addr(&dev->virtio_pci_dev, vq);
94+
vm_ioeventfd_register(v, dev->rx_ioeventfd, addr, NOTIFY_OFFSET, 0);
95+
pthread_create(&dev->rx_thread, NULL, virtio_net_vq_avail_handler_rx,
96+
(void *) vq);
97+
}
98+
99+
static void virtio_net_enable_vq_tx(struct virtq *vq)
100+
{
101+
struct virtio_net_dev *dev = (struct virtio_net_dev *) vq->dev;
102+
vm_t *v = container_of(dev, vm_t, virtio_net_dev);
103+
104+
if (vq->info.enable)
105+
return;
106+
vq->info.enable = true;
107+
vq->desc_ring =
108+
(struct vring_packed_desc *) vm_guest_to_host(v, vq->info.desc_addr);
109+
vq->device_event = (struct vring_packed_desc_event *) vm_guest_to_host(
110+
v, vq->info.device_addr);
111+
vq->guest_event = (struct vring_packed_desc_event *) vm_guest_to_host(
112+
v, vq->info.driver_addr);
113+
114+
uint64_t addr = virtio_pci_get_notify_addr(&dev->virtio_pci_dev, vq);
115+
vm_ioeventfd_register(v, dev->tx_ioeventfd, addr, NOTIFY_OFFSET, 0);
116+
pthread_create(&dev->tx_thread, NULL, virtio_net_vq_avail_handler_tx,
117+
(void *) vq);
118+
}
119+
120+
static void virtio_net_notify_used_rx(struct virtq *vq)
121+
{
122+
struct virtio_net_dev *dev = (struct virtio_net_dev *) vq->dev;
123+
uint64_t n = 1;
124+
if (write(dev->irqfd, &n, sizeof(n)) < 0)
125+
throw_err("Failed to write the irqfd");
126+
}
127+
128+
static void virtio_net_notify_used_tx(struct virtq *vq)
129+
{
130+
struct virtio_net_dev *dev = (struct virtio_net_dev *) vq->dev;
131+
uint64_t n = 1;
132+
133+
if (write(dev->irqfd, &n, sizeof(n)) < 0)
134+
throw_err("Failed to write the irqfd");
135+
}
136+
137+
void virtio_net_complete_request_rx(struct virtq *vq)
138+
{
139+
struct virtio_net_dev *dev = (struct virtio_net_dev *) vq->dev;
140+
vm_t *v = container_of(dev, vm_t, virtio_net_dev);
141+
struct vring_packed_desc *desc;
142+
143+
while ((desc = virtq_get_avail(vq)) != NULL) {
144+
uint8_t *data = vm_guest_to_host(v, desc->addr);
145+
struct virtio_net_hdr_v1 *virtio_hdr =
146+
(struct virtio_net_hdr_v1 *) data;
147+
memset(virtio_hdr, 0, sizeof(struct virtio_net_hdr_v1));
148+
149+
virtio_hdr->num_buffers = 1;
150+
151+
size_t virtio_header_len = sizeof(struct virtio_net_hdr_v1);
152+
ssize_t read_bytes = read(dev->tapfd, data + virtio_header_len,
153+
desc->len - virtio_header_len);
154+
if (read_bytes < 0) {
155+
vq->guest_event->flags = VRING_PACKED_EVENT_FLAG_DISABLE;
156+
return;
157+
}
158+
desc->len = virtio_header_len + read_bytes;
159+
160+
desc->flags ^= (1ULL << VRING_PACKED_DESC_F_USED);
161+
dev->virtio_pci_dev.config.isr_cap.isr_status |= VIRTIO_PCI_ISR_QUEUE;
162+
return;
163+
}
164+
vq->guest_event->flags = VRING_PACKED_EVENT_FLAG_DISABLE;
165+
return;
166+
}
167+
168+
void virtio_net_complete_request_tx(struct virtq *vq)
169+
{
170+
struct virtio_net_dev *dev = (struct virtio_net_dev *) vq->dev;
171+
vm_t *v = container_of(dev, vm_t, virtio_net_dev);
172+
struct vring_packed_desc *desc;
173+
while ((desc = virtq_get_avail(vq)) != NULL) {
174+
uint8_t *data = vm_guest_to_host(v, desc->addr);
175+
size_t virtio_header_len = sizeof(struct virtio_net_hdr_v1);
176+
177+
if (desc->len < virtio_header_len) {
178+
vq->guest_event->flags = VRING_PACKED_EVENT_FLAG_DISABLE;
179+
return;
180+
}
181+
182+
uint8_t *actual_data = data + virtio_header_len;
183+
size_t actual_data_len = desc->len - virtio_header_len;
184+
185+
struct iovec iov[1];
186+
iov[0].iov_base = actual_data;
187+
iov[0].iov_len = actual_data_len;
188+
189+
ssize_t write_bytes = writev(dev->tapfd, iov, 1);
190+
if (write_bytes < 0) {
191+
vq->guest_event->flags = VRING_PACKED_EVENT_FLAG_DISABLE;
192+
return;
193+
}
194+
desc->flags ^= (1ULL << VRING_PACKED_DESC_F_USED);
195+
dev->virtio_pci_dev.config.isr_cap.isr_status |= VIRTIO_PCI_ISR_QUEUE;
196+
return;
197+
}
198+
vq->guest_event->flags = VRING_PACKED_EVENT_FLAG_DISABLE;
199+
return;
200+
}
201+
202+
static struct virtq_ops virtio_net_ops[VIRTIO_NET_VIRTQ_NUM] = {
203+
[VIRTQ_RX] = {.enable_vq = virtio_net_enable_vq_rx,
204+
.complete_request = virtio_net_complete_request_rx,
205+
.notify_used = virtio_net_notify_used_rx},
206+
[VIRTQ_TX] = {.enable_vq = virtio_net_enable_vq_tx,
207+
.complete_request = virtio_net_complete_request_tx,
208+
.notify_used = virtio_net_notify_used_tx},
209+
};
210+
211+
bool virtio_net_init(struct virtio_net_dev *virtio_net_dev)
212+
{
213+
memset(virtio_net_dev, 0x00, sizeof(struct virtio_net_dev));
214+
215+
virtio_net_dev->tapfd = open("/dev/net/tun", O_RDWR);
216+
if (virtio_net_dev->tapfd < 0) {
217+
return false;
218+
}
219+
struct ifreq ifreq = {.ifr_flags = IFF_TAP | IFF_NO_PI};
220+
strncpy(ifreq.ifr_name, TAP_INTERFACE, sizeof(ifreq.ifr_name));
221+
if (ioctl(virtio_net_dev->tapfd, TUNSETIFF, &ifreq) < 0) {
222+
fprintf(stderr, "failed to allocate TAP device: %s\n", strerror(errno));
223+
close(virtio_net_dev->tapfd);
224+
return false;
225+
}
226+
int flags = fcntl(virtio_net_dev->tapfd, F_GETFL, 0);
227+
flags |= O_NONBLOCK;
228+
if (fcntl(virtio_net_dev->tapfd, F_SETFL, flags) == -1) {
229+
fprintf(stderr, "failed to set flags on TAP device: %s\n",
230+
strerror(errno));
231+
close(virtio_net_dev->tapfd);
232+
return false;
233+
}
234+
return true;
235+
}
236+
237+
static void virtio_net_setup(struct virtio_net_dev *dev)
238+
{
239+
vm_t *v = container_of(dev, vm_t, virtio_net_dev);
240+
241+
dev->enable = true;
242+
dev->irq_num = VIRTIO_NET_IRQ;
243+
dev->rx_ioeventfd = eventfd(0, EFD_CLOEXEC);
244+
dev->tx_ioeventfd = eventfd(0, EFD_CLOEXEC);
245+
dev->irqfd = eventfd(0, EFD_CLOEXEC);
246+
vm_irqfd_register(v, dev->irqfd, dev->irq_num, 0);
247+
for (int i = 0; i < VIRTIO_NET_VIRTQ_NUM; i++) {
248+
struct virtq_ops *ops = &virtio_net_ops[i];
249+
dev->vq[i].info.notify_off = i;
250+
virtq_init(&dev->vq[i], dev, ops);
251+
}
252+
}
253+
254+
void virtio_net_init_pci(struct virtio_net_dev *virtio_net_dev,
255+
struct pci *pci,
256+
struct bus *io_bus,
257+
struct bus *mmio_bus)
258+
{
259+
struct virtio_pci_dev *dev = &virtio_net_dev->virtio_pci_dev;
260+
virtio_net_setup(virtio_net_dev);
261+
virtio_pci_init(dev, pci, io_bus, mmio_bus);
262+
virtio_pci_set_dev_cfg(dev, &virtio_net_dev->config,
263+
sizeof(virtio_net_dev->config));
264+
virtio_pci_set_pci_hdr(dev, VIRTIO_PCI_DEVICE_ID_NET, VIRTIO_NET_PCI_CLASS,
265+
virtio_net_dev->irq_num);
266+
dev->notify_cap->notify_off_multiplier = NOTIFY_OFFSET;
267+
virtio_pci_set_virtq(dev, virtio_net_dev->vq, VIRTIO_NET_VIRTQ_NUM);
268+
269+
virtio_pci_add_feature(dev, VIRTIO_NET_F_MQ);
270+
virtio_pci_enable(dev);
271+
}
272+
273+
void virtio_net_exit(struct virtio_net_dev *dev)
274+
{
275+
if (!dev->enable)
276+
return;
277+
__atomic_store_n(&thread_stop, true, __ATOMIC_RELAXED);
278+
pthread_join(dev->rx_thread, NULL);
279+
pthread_join(dev->tx_thread, NULL);
280+
virtio_pci_exit(&dev->virtio_pci_dev);
281+
close(dev->irqfd);
282+
close(dev->rx_ioeventfd);
283+
close(dev->tx_ioeventfd);
284+
close(dev->tapfd);
285+
}

0 commit comments

Comments
 (0)