/* * 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; }