aboutsummaryrefslogtreecommitdiff
path: root/hw/virtio-net.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/virtio-net.c')
-rw-r--r--hw/virtio-net.c176
1 files changed, 97 insertions, 79 deletions
diff --git a/hw/virtio-net.c b/hw/virtio-net.c
index 50ba728c02..108ce07cfc 100644
--- a/hw/virtio-net.c
+++ b/hw/virtio-net.c
@@ -41,6 +41,8 @@ typedef struct VirtIONet
int32_t tx_burst;
int tx_waiting;
uint32_t has_vnet_hdr;
+ size_t host_hdr_len;
+ size_t guest_hdr_len;
uint8_t has_ufo;
struct {
VirtQueueElement elem;
@@ -200,16 +202,19 @@ static void virtio_net_reset(VirtIODevice *vdev)
memset(n->vlans, 0, MAX_VLAN >> 3);
}
-static int peer_has_vnet_hdr(VirtIONet *n)
+static void peer_test_vnet_hdr(VirtIONet *n)
{
if (!n->nic->nc.peer)
- return 0;
+ return;
if (n->nic->nc.peer->info->type != NET_CLIENT_OPTIONS_KIND_TAP)
- return 0;
+ return;
n->has_vnet_hdr = tap_has_vnet_hdr(n->nic->nc.peer);
+}
+static int peer_has_vnet_hdr(VirtIONet *n)
+{
return n->has_vnet_hdr;
}
@@ -223,15 +228,27 @@ static int peer_has_ufo(VirtIONet *n)
return n->has_ufo;
}
+static void virtio_net_set_mrg_rx_bufs(VirtIONet *n, int mergeable_rx_bufs)
+{
+ n->mergeable_rx_bufs = mergeable_rx_bufs;
+
+ n->guest_hdr_len = n->mergeable_rx_bufs ?
+ sizeof(struct virtio_net_hdr_mrg_rxbuf) : sizeof(struct virtio_net_hdr);
+
+ if (peer_has_vnet_hdr(n) &&
+ tap_has_vnet_hdr_len(n->nic->nc.peer, n->guest_hdr_len)) {
+ tap_set_vnet_hdr_len(n->nic->nc.peer, n->guest_hdr_len);
+ n->host_hdr_len = n->guest_hdr_len;
+ }
+}
+
static uint32_t virtio_net_get_features(VirtIODevice *vdev, uint32_t features)
{
VirtIONet *n = to_virtio_net(vdev);
features |= (1 << VIRTIO_NET_F_MAC);
- if (peer_has_vnet_hdr(n)) {
- tap_using_vnet_hdr(n->nic->nc.peer, 1);
- } else {
+ if (!peer_has_vnet_hdr(n)) {
features &= ~(0x1 << VIRTIO_NET_F_CSUM);
features &= ~(0x1 << VIRTIO_NET_F_HOST_TSO4);
features &= ~(0x1 << VIRTIO_NET_F_HOST_TSO6);
@@ -277,7 +294,7 @@ static void virtio_net_set_features(VirtIODevice *vdev, uint32_t features)
{
VirtIONet *n = to_virtio_net(vdev);
- n->mergeable_rx_bufs = !!(features & (1 << VIRTIO_NET_F_MRG_RXBUF));
+ virtio_net_set_mrg_rx_bufs(n, !!(features & (1 << VIRTIO_NET_F_MRG_RXBUF)));
if (n->has_vnet_hdr) {
tap_set_offload(n->nic->nc.peer,
@@ -499,41 +516,34 @@ static int virtio_net_has_buffers(VirtIONet *n, int bufsize)
* cache.
*/
static void work_around_broken_dhclient(struct virtio_net_hdr *hdr,
- const uint8_t *buf, size_t size)
+ uint8_t *buf, size_t size)
{
if ((hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) && /* missing csum */
(size > 27 && size < 1500) && /* normal sized MTU */
(buf[12] == 0x08 && buf[13] == 0x00) && /* ethertype == IPv4 */
(buf[23] == 17) && /* ip.protocol == UDP */
(buf[34] == 0 && buf[35] == 67)) { /* udp.srcport == bootps */
- /* FIXME this cast is evil */
- net_checksum_calculate((uint8_t *)buf, size);
+ net_checksum_calculate(buf, size);
hdr->flags &= ~VIRTIO_NET_HDR_F_NEEDS_CSUM;
}
}
-static int receive_header(VirtIONet *n, struct iovec *iov, int iovcnt,
- const void *buf, size_t size, size_t hdr_len)
+static void receive_header(VirtIONet *n, const struct iovec *iov, int iov_cnt,
+ const void *buf, size_t size)
{
- struct virtio_net_hdr *hdr = (struct virtio_net_hdr *)iov[0].iov_base;
- int offset = 0;
-
- hdr->flags = 0;
- hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE;
-
if (n->has_vnet_hdr) {
- memcpy(hdr, buf, sizeof(*hdr));
- offset = sizeof(*hdr);
- work_around_broken_dhclient(hdr, buf + offset, size - offset);
+ /* FIXME this cast is evil */
+ void *wbuf = (void *)buf;
+ work_around_broken_dhclient(wbuf, wbuf + n->host_hdr_len,
+ size - n->host_hdr_len);
+ iov_from_buf(iov, iov_cnt, 0, buf, sizeof(struct virtio_net_hdr));
+ } else {
+ struct virtio_net_hdr hdr = {
+ .flags = 0,
+ .gso_type = VIRTIO_NET_HDR_GSO_NONE
+ };
+ iov_from_buf(iov, iov_cnt, 0, &hdr, sizeof hdr);
}
-
- /* We only ever receive a struct virtio_net_hdr from the tapfd,
- * but we may be passing along a larger header to the guest.
- */
- iov[0].iov_base += hdr_len;
- iov[0].iov_len -= hdr_len;
-
- return offset;
}
static int receive_filter(VirtIONet *n, const uint8_t *buf, int size)
@@ -546,9 +556,7 @@ static int receive_filter(VirtIONet *n, const uint8_t *buf, int size)
if (n->promisc)
return 1;
- if (n->has_vnet_hdr) {
- ptr += sizeof(struct virtio_net_hdr);
- }
+ ptr += n->host_hdr_len;
if (!memcmp(&ptr[12], vlan, sizeof(vlan))) {
int vid = be16_to_cpup((uint16_t *)(ptr + 14)) & 0xfff;
@@ -592,19 +600,16 @@ static int receive_filter(VirtIONet *n, const uint8_t *buf, int size)
static ssize_t virtio_net_receive(NetClientState *nc, const uint8_t *buf, size_t size)
{
VirtIONet *n = DO_UPCAST(NICState, nc, nc)->opaque;
- struct virtio_net_hdr_mrg_rxbuf *mhdr = NULL;
- size_t guest_hdr_len, offset, i, host_hdr_len;
+ struct iovec mhdr_sg[VIRTQUEUE_MAX_SIZE];
+ struct virtio_net_hdr_mrg_rxbuf mhdr;
+ unsigned mhdr_cnt = 0;
+ size_t offset, i, guest_offset;
if (!virtio_net_can_receive(&n->nic->nc))
return -1;
/* hdr_len refers to the header we supply to the guest */
- guest_hdr_len = n->mergeable_rx_bufs ?
- sizeof(struct virtio_net_hdr_mrg_rxbuf) : sizeof(struct virtio_net_hdr);
-
-
- host_hdr_len = n->has_vnet_hdr ? sizeof(struct virtio_net_hdr) : 0;
- if (!virtio_net_has_buffers(n, size + guest_hdr_len - host_hdr_len))
+ if (!virtio_net_has_buffers(n, size + n->guest_hdr_len - n->host_hdr_len))
return 0;
if (!receive_filter(n, buf, size))
@@ -615,7 +620,7 @@ static ssize_t virtio_net_receive(NetClientState *nc, const uint8_t *buf, size_t
while (offset < size) {
VirtQueueElement elem;
int len, total;
- struct iovec sg[VIRTQUEUE_MAX_SIZE];
+ const struct iovec *sg = elem.in_sg;
total = 0;
@@ -626,7 +631,7 @@ static ssize_t virtio_net_receive(NetClientState *nc, const uint8_t *buf, size_t
"i %zd mergeable %d offset %zd, size %zd, "
"guest hdr len %zd, host hdr len %zd guest features 0x%x",
i, n->mergeable_rx_bufs, offset, size,
- guest_hdr_len, host_hdr_len, n->vdev.guest_features);
+ n->guest_hdr_len, n->host_hdr_len, n->vdev.guest_features);
exit(1);
}
@@ -635,24 +640,25 @@ static ssize_t virtio_net_receive(NetClientState *nc, const uint8_t *buf, size_t
exit(1);
}
- if (!n->mergeable_rx_bufs && elem.in_sg[0].iov_len != guest_hdr_len) {
- error_report("virtio-net header not in first element");
- exit(1);
- }
-
- memcpy(&sg, &elem.in_sg[0], sizeof(sg[0]) * elem.in_num);
-
if (i == 0) {
- if (n->mergeable_rx_bufs)
- mhdr = (struct virtio_net_hdr_mrg_rxbuf *)sg[0].iov_base;
+ assert(offset == 0);
+ if (n->mergeable_rx_bufs) {
+ mhdr_cnt = iov_copy(mhdr_sg, ARRAY_SIZE(mhdr_sg),
+ sg, elem.in_num,
+ offsetof(typeof(mhdr), num_buffers),
+ sizeof(mhdr.num_buffers));
+ }
- offset += receive_header(n, sg, elem.in_num,
- buf + offset, size - offset, guest_hdr_len);
- total += guest_hdr_len;
+ receive_header(n, sg, elem.in_num, buf, size);
+ offset = n->host_hdr_len;
+ total += n->guest_hdr_len;
+ guest_offset = n->guest_hdr_len;
+ } else {
+ guest_offset = 0;
}
/* copy in packet. ugh */
- len = iov_from_buf(sg, elem.in_num, 0,
+ len = iov_from_buf(sg, elem.in_num, guest_offset,
buf + offset, size - offset);
total += len;
offset += len;
@@ -665,7 +671,7 @@ static ssize_t virtio_net_receive(NetClientState *nc, const uint8_t *buf, size_t
"i %zd mergeable %d offset %zd, size %zd, "
"guest hdr len %zd, host hdr len %zd",
i, n->mergeable_rx_bufs,
- offset, size, guest_hdr_len, host_hdr_len);
+ offset, size, n->guest_hdr_len, n->host_hdr_len);
#endif
return size;
}
@@ -674,8 +680,11 @@ static ssize_t virtio_net_receive(NetClientState *nc, const uint8_t *buf, size_t
virtqueue_fill(n->rx_vq, &elem, total, i++);
}
- if (mhdr) {
- stw_p(&mhdr->num_buffers, i);
+ if (mhdr_cnt) {
+ stw_p(&mhdr.num_buffers, i);
+ iov_from_buf(mhdr_sg, mhdr_cnt,
+ 0,
+ &mhdr.num_buffers, sizeof mhdr.num_buffers);
}
virtqueue_flush(n->rx_vq, i);
@@ -716,33 +725,35 @@ static int32_t virtio_net_flush_tx(VirtIONet *n, VirtQueue *vq)
}
while (virtqueue_pop(vq, &elem)) {
- ssize_t ret, len = 0;
+ ssize_t ret, len;
unsigned int out_num = elem.out_num;
struct iovec *out_sg = &elem.out_sg[0];
- unsigned hdr_len;
-
- /* hdr_len refers to the header received from the guest */
- hdr_len = n->mergeable_rx_bufs ?
- sizeof(struct virtio_net_hdr_mrg_rxbuf) :
- sizeof(struct virtio_net_hdr);
+ struct iovec sg[VIRTQUEUE_MAX_SIZE];
- if (out_num < 1 || out_sg->iov_len != hdr_len) {
+ if (out_num < 1) {
error_report("virtio-net header not in first element");
exit(1);
}
- /* ignore the header if GSO is not supported */
- if (!n->has_vnet_hdr) {
- out_num--;
- out_sg++;
- len += hdr_len;
- } else if (n->mergeable_rx_bufs) {
- /* tapfd expects a struct virtio_net_hdr */
- hdr_len -= sizeof(struct virtio_net_hdr);
- out_sg->iov_len -= hdr_len;
- len += hdr_len;
+ /*
+ * If host wants to see the guest header as is, we can
+ * pass it on unchanged. Otherwise, copy just the parts
+ * that host is interested in.
+ */
+ assert(n->host_hdr_len <= n->guest_hdr_len);
+ if (n->host_hdr_len != n->guest_hdr_len) {
+ unsigned sg_num = iov_copy(sg, ARRAY_SIZE(sg),
+ out_sg, out_num,
+ 0, n->host_hdr_len);
+ sg_num += iov_copy(sg + sg_num, ARRAY_SIZE(sg) - sg_num,
+ out_sg, out_num,
+ n->guest_hdr_len, -1);
+ out_num = sg_num;
+ out_sg = sg;
}
+ len = n->guest_hdr_len;
+
ret = qemu_sendv_packet_async(&n->nic->nc, out_sg, out_num,
virtio_net_tx_complete);
if (ret == 0) {
@@ -899,7 +910,8 @@ static int virtio_net_load(QEMUFile *f, void *opaque, int version_id)
qemu_get_buffer(f, n->mac, ETH_ALEN);
n->tx_waiting = qemu_get_be32(f);
- n->mergeable_rx_bufs = qemu_get_be32(f);
+
+ virtio_net_set_mrg_rx_bufs(n, qemu_get_be32(f));
if (version_id >= 3)
n->status = qemu_get_be16(f);
@@ -939,7 +951,6 @@ static int virtio_net_load(QEMUFile *f, void *opaque, int version_id)
}
if (n->has_vnet_hdr) {
- tap_using_vnet_hdr(n->nic->nc.peer, 1);
tap_set_offload(n->nic->nc.peer,
(n->vdev.guest_features >> VIRTIO_NET_F_GUEST_CSUM) & 1,
(n->vdev.guest_features >> VIRTIO_NET_F_GUEST_TSO4) & 1,
@@ -1038,12 +1049,19 @@ VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf,
n->status = VIRTIO_NET_S_LINK_UP;
n->nic = qemu_new_nic(&net_virtio_info, conf, object_get_typename(OBJECT(dev)), dev->id, n);
+ peer_test_vnet_hdr(n);
+ if (peer_has_vnet_hdr(n)) {
+ tap_using_vnet_hdr(n->nic->nc.peer, 1);
+ n->host_hdr_len = sizeof(struct virtio_net_hdr);
+ } else {
+ n->host_hdr_len = 0;
+ }
qemu_format_nic_info_str(&n->nic->nc, conf->macaddr.a);
n->tx_waiting = 0;
n->tx_burst = net->txburst;
- n->mergeable_rx_bufs = 0;
+ virtio_net_set_mrg_rx_bufs(n, 0);
n->promisc = 1; /* for compatibility */
n->mac_table.macs = g_malloc0(MAC_TABLE_ENTRIES * ETH_ALEN);