aboutsummaryrefslogtreecommitdiff
path: root/pc-bios/s390-ccw/virtio-net.c
diff options
context:
space:
mode:
Diffstat (limited to 'pc-bios/s390-ccw/virtio-net.c')
-rw-r--r--pc-bios/s390-ccw/virtio-net.c135
1 files changed, 135 insertions, 0 deletions
diff --git a/pc-bios/s390-ccw/virtio-net.c b/pc-bios/s390-ccw/virtio-net.c
new file mode 100644
index 0000000000..ff7f4dad25
--- /dev/null
+++ b/pc-bios/s390-ccw/virtio-net.c
@@ -0,0 +1,135 @@
+/*
+ * 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;
+}