/*
* Virtio-net driver for the s390-ccw firmware
*
* Copyright 2017 Thomas Huth, Red Hat Inc.
*
* This code 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.
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <ethernet.h>
#include "s390-ccw.h"
#include "virtio.h"
#ifndef DEBUG_VIRTIO_NET
#define DEBUG_VIRTIO_NET 0
#endif
#define VIRTIO_NET_F_MAC_BIT (1 << 5)
#define VQ_RX 0 /* Receive queue */
#define VQ_TX 1 /* Transmit queue */
struct VirtioNetHdr {
uint8_t flags;
uint8_t gso_type;
uint16_t hdr_len;
uint16_t gso_size;
uint16_t csum_start;
uint16_t csum_offset;
/*uint16_t num_buffers;*/ /* Only with VIRTIO_NET_F_MRG_RXBUF or VIRTIO1 */
};
typedef struct VirtioNetHdr VirtioNetHdr;
static uint16_t rx_last_idx; /* Last index in receive queue "used" ring */
int virtio_net_init(void *mac_addr)
{
VDev *vdev = virtio_get_device();
VRing *rxvq = &vdev->vrings[VQ_RX];
void *buf;
int i;
vdev->guest_features[0] = VIRTIO_NET_F_MAC_BIT;
virtio_setup_ccw(vdev);
IPL_assert(vdev->guest_features[0] & VIRTIO_NET_F_MAC_BIT,
"virtio-net device does not support the MAC address feature");
memcpy(mac_addr, vdev->config.net.mac, ETH_ALEN);
for (i = 0; i < 64; i++) {
buf = malloc(ETH_MTU_SIZE + sizeof(VirtioNetHdr));
IPL_assert(buf != NULL, "Can not allocate memory for receive buffers");
vring_send_buf(rxvq, buf, ETH_MTU_SIZE + sizeof(VirtioNetHdr),
VRING_DESC_F_WRITE);
}
vring_notify(rxvq);
return 0;
}
int send(int fd, const void *buf, int len, int flags)
{
VirtioNetHdr tx_hdr;
VDev *vdev = virtio_get_device();
VRing *txvq = &vdev->vrings[VQ_TX];
/* Set up header - we do not use anything special, so simply clear it */
memset(&tx_hdr, 0, sizeof(tx_hdr));
vring_send_buf(txvq, &tx_hdr, sizeof(tx_hdr), VRING_DESC_F_NEXT);
vring_send_buf(txvq, (void *)buf, len, VRING_HIDDEN_IS_CHAIN);
while (!vr_poll(txvq)) {
yield();
}
if (drain_irqs(txvq->schid)) {
puts("send: drain irqs failed");
return -1;
}
return len;
}
int recv(int fd, void *buf, int maxlen, int flags)
{
VDev *vdev = virtio_get_device();
VRing *rxvq = &vdev->vrings[VQ_RX];
int len, id;
uint8_t *pkt;
if (rx_last_idx == rxvq->used->idx) {
return 0;
}
len = rxvq->used->ring[rx_last_idx % rxvq->num].len - sizeof(VirtioNetHdr);
if (len > maxlen) {
puts("virtio-net: Receive buffer too small");
len = maxlen;
}
id = rxvq->used->ring[rx_last_idx % rxvq->num].id % rxvq->num;
pkt = (uint8_t *)(rxvq->desc[id].addr + sizeof(VirtioNetHdr));
#if DEBUG_VIRTIO_NET /* Dump packet */
int i;
printf("\nbuf %p: len=%i\n", (void *)rxvq->desc[id].addr, len);
for (i = 0; i < 64; i++) {
printf(" %02x", pkt[i]);
if ((i % 16) == 15) {
printf("\n");
}
}
printf("\n");
#endif
/* Copy data to destination buffer */
memcpy(buf, pkt, len);
/* Mark buffer as available to the host again */
rxvq->avail->ring[rxvq->avail->idx % rxvq->num] = id;
rxvq->avail->idx = rxvq->avail->idx + 1;
vring_notify(rxvq);
/* Move index to next entry */
rx_last_idx = rx_last_idx + 1;
return len;
}