blob: 1a4ecad4f204a2a3b100b59f162ae1f5cee552c8 [file] [log] [blame] [edit]
/* Virtio helper functions from linux/tools/lguest/lguest.c
*
* Copyright (C) 1991-2016, the Linux Kernel authors
* Copyright (c) 2016 Google Inc.
*
* Author:
* Rusty Russell <rusty@rustcorp.com.au>
* Kyle Milka <kmilka@google.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* The code from lguest.c has been modified for Akaros.
*
* Original linux/tools/lguest/lguest.c:
* https://github.com/torvalds/linux/blob/v4.5/tools/lguest/lguest.c
* most recent hash on the file as of v4.5 tag:
* e523caa601f4a7c2fa1ecd040db921baf7453798
*/
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <vmm/virtio.h>
#include <vmm/virtio_mmio.h>
#include <vmm/virtio_net.h>
#define VIRTIO_HEADER_SIZE 12
static int ctlfd;
static int etherfd;
static char data_path[128];
static char clone_path[64];
void net_init_fn(struct virtio_vq_dev *vqdev, int nic)
{
char type[] = "connect -1";
char buf[8];
char addr_path[32];
char addr_buf[3];
int addr_fd;
uint8_t addr_bytes;
int num_read;
int total_read = 0;
snprintf(addr_path, sizeof(addr_path), "/net/ether%d/addr", nic);
addr_fd = open(addr_path, O_RDONLY);
if (addr_fd < 0)
VIRTIO_DEV_ERRX(vqdev, "Bad addr_fd\n");
for (int i = 0; i < ETH_ALEN; ++i) {
assert(read(addr_fd, addr_buf, 2) == 2);
addr_buf[2] = 0;
addr_bytes = (uint8_t)(strtol(addr_buf, 0, 16));
((struct virtio_net_config *)(vqdev->cfg))->mac[i] = addr_bytes;
((struct virtio_net_config *)(vqdev->cfg_d))->mac[i] = addr_bytes;
}
snprintf(clone_path, sizeof(clone_path), "/net/ether%d/clone", nic);
ctlfd = open(clone_path, O_RDWR);
if (ctlfd < 0)
VIRTIO_DEV_ERRX(vqdev, "%s", clone_path);
do {
num_read = read(ctlfd, buf + total_read, sizeof(buf) - total_read);
total_read += num_read;
} while (num_read > 0);
etherfd = strtol(buf, 0, 10);
if (etherfd < 0)
VIRTIO_DEV_ERRX(vqdev, "bad etherfd %d (%s)", etherfd, buf);
snprintf(data_path, sizeof(data_path),
"/net/ether%d/%d/data", nic, etherfd);
if (write(ctlfd, type, sizeof(type)) != sizeof(type))
VIRTIO_DEV_ERRX(vqdev, "write to ctlfd failed");
}
/* net_receiveq_fn receives packets for the guest through the virtio networking
* device and the _vq virtio queue.
*/
void net_receiveq_fn(void *_vq)
{
struct virtio_vq *vq = _vq;
uint32_t head;
uint32_t olen, ilen;
int num_read;
struct iovec *iov;
struct virtio_mmio_dev *dev = vq->vqdev->transport_dev;
int fd;
struct virtio_net_hdr_v1 *net_header;
fd = open(data_path, O_RDWR);
if (fd == -1)
VIRTIO_DEV_ERRX(vq->vqdev, "Could not open data file for ether1.");
if (!vq)
VIRTIO_DEV_ERRX(vq->vqdev,
"\n %s:%d\n"
" Virtio device: (not sure which one): Error, device behavior.\n"
" The device must provide a valid virtio_vq as an argument to %s."
, __FILE__, __LINE__, __func__);
if (vq->qready == 0x0)
VIRTIO_DEV_ERRX(vq->vqdev,
"The service function for queue '%s' was launched before the driver set QueueReady to 0x1.",
vq->name);
iov = malloc(vq->qnum_max * sizeof(struct iovec));
assert(iov != NULL);
if (!dev->poke_guest) {
free(iov);
VIRTIO_DEV_ERRX(vq->vqdev,
"The 'poke_guest' function pointer was not set.");
}
for (;;) {
head = virtio_next_avail_vq_desc(vq, iov, &olen, &ilen);
if (olen) {
free(iov);
VIRTIO_DRI_ERRX(vq->vqdev,
"The driver placed a device-readable buffer in the net device's receiveq.\n"
" See virtio-v1.0-cs04 s5.3.6.1 Device Operation");
}
/* For receive the virtio header is in iov[0], so we only want
* the packet to be read into iov[1] and above.
*/
num_read = readv(fd, iov + 1, ilen - 1);
if (num_read < 0) {
free(iov);
VIRTIO_DEV_ERRX(vq->vqdev,
"Encountered an error trying to read input from the ethernet device.");
}
/* See virtio spec virtio-v1.0-cs04 s5.1.6.3.2 Device Requirements:
* Setting Up Receive Buffers
*
* VIRTIO_NET_F_MRG_RXBUF is not currently negotiated.
* num_buffers will always be 1 if VIRTIO_NET_F_MRG_RXBUF is not
* negotiated.
*/
net_header = iov[0].iov_base;
net_header->num_buffers = 1;
virtio_add_used_desc(vq, head, num_read + VIRTIO_HEADER_SIZE);
virtio_mmio_set_vring_irq(dev);
dev->poke_guest(dev->vec);
}
}
/* net_transmitq_fn transmits packets from the guest through the virtio
* networking device through the _vq virtio queue.
*/
void net_transmitq_fn(void *_vq)
{
struct virtio_vq *vq = _vq;
uint32_t head;
uint32_t olen, ilen;
struct iovec *iov;
struct virtio_mmio_dev *dev = vq->vqdev->transport_dev;
void *stripped;
int ret;
int fd = open(data_path, O_RDWR);
if (fd == -1)
VIRTIO_DEV_ERRX(vq->vqdev, "Could not open data file for ether1.");
iov = malloc(vq->qnum_max * sizeof(struct iovec));
assert(iov != NULL);
if (!dev->poke_guest) {
free(iov);
VIRTIO_DEV_ERRX(vq->vqdev,
"The 'poke_guest' function pointer was not set.");
}
for (;;) {
head = virtio_next_avail_vq_desc(vq, iov, &olen, &ilen);
if (ilen) {
free(iov);
VIRTIO_DRI_ERRX(vq->vqdev,
"The driver placed a device-writeable buffer in the network device's transmitq.\n"
" See virtio-v1.0-cs04 s5.3.6.1 Device Operation");
}
/* Strip off the virtio header (the first 12 bytes), as it is
* not a part of the actual ethernet frame.
*/
for (int i = 0; i < olen; i++) {
stripped = iov[i].iov_base + VIRTIO_HEADER_SIZE;
ret = write(fd, stripped, iov[i].iov_len - VIRTIO_HEADER_SIZE);
assert(ret == iov[i].iov_len - VIRTIO_HEADER_SIZE);
}
virtio_add_used_desc(vq, head, 0);
virtio_mmio_set_vring_irq(dev);
dev->poke_guest(dev->vec);
}
}