static ssize_t virtio_net_receive(NetClientState *nc, const uint8_t *buf, size_t size) { VirtIONet *n = qemu_get_nic_opaque(nc); VirtIONetQueue *q = virtio_net_get_subqueue(nc); VirtIODevice *vdev = VIRTIO_DEVICE(n); 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(nc)) { return -1; } /* hdr_len refers to the header we supply to the guest */ if (!virtio_net_has_buffers(q, size + n->guest_hdr_len - n->host_hdr_len)) { return 0; } if (!receive_filter(n, buf, size)) return size; offset = i = 0; while (offset < size) { VirtQueueElement elem; int len, total; const struct iovec *sg = elem.in_sg; total = 0; if (virtqueue_pop(q->rx_vq, &elem) == 0) { if (i == 0) return -1; error_report("virtio-net unexpected empty queue: " "i %zd mergeable %d offset %zd, size %zd, " "guest hdr len %zd, host hdr len %zd " "guest features 0x%" PRIx64, i, n->mergeable_rx_bufs, offset, size, n->guest_hdr_len, n->host_hdr_len, vdev->guest_features); exit(1); } if (elem.in_num < 1) { error_report("virtio-net receive queue contains no in buffers"); exit(1); } if (i == 0) { 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)); } 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, guest_offset, buf + offset, size - offset); total += len; offset += len; /* If buffers can't be merged, at this point we * must have consumed the complete packet. * Otherwise, drop it. */ if (!n->mergeable_rx_bufs && offset < size) { virtqueue_discard(q->rx_vq, &elem, total); return size; } /* signal other side */ virtqueue_fill(q->rx_vq, &elem, total, i++); } if (mhdr_cnt) { virtio_stw_p(vdev, &mhdr.num_buffers, i); iov_from_buf(mhdr_sg, mhdr_cnt, 0, &mhdr.num_buffers, sizeof mhdr.num_buffers); } virtqueue_flush(q->rx_vq, i); virtio_notify(vdev, q->rx_vq); return size; }