X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/dc14a397812b91dd0d48b03d1b8f66a251542369..34f9f0b5802a6ff9ec0be00810f7910d3935df3e:/hw/virtio-net.c diff --git a/hw/virtio-net.c b/hw/virtio-net.c index acb3cec7cb..ccb3e632a4 100644 --- a/hw/virtio-net.c +++ b/hw/virtio-net.c @@ -11,6 +11,7 @@ * */ +#include "iov.h" #include "virtio.h" #include "net.h" #include "net/checksum.h" @@ -35,7 +36,10 @@ typedef struct VirtIONet VirtQueue *ctrl_vq; NICState *nic; QEMUTimer *tx_timer; - int tx_timer_active; + QEMUBH *tx_bh; + uint32_t tx_timeout; + int32_t tx_burst; + int tx_waiting; uint32_t has_vnet_hdr; uint8_t has_ufo; struct { @@ -50,7 +54,6 @@ typedef struct VirtIONet uint8_t nouni; uint8_t nobcast; uint8_t vhost_started; - VMChangeStateEntry *vmstate; struct { int in_use; int first_multi; @@ -59,6 +62,7 @@ typedef struct VirtIONet uint8_t *macs; } mac_table; uint32_t *vlans; + DeviceState *qdev; } VirtIONet; /* TODO @@ -93,6 +97,67 @@ static void virtio_net_set_config(VirtIODevice *vdev, const uint8_t *config) } } +static bool virtio_net_started(VirtIONet *n, uint8_t status) +{ + return (status & VIRTIO_CONFIG_S_DRIVER_OK) && + (n->status & VIRTIO_NET_S_LINK_UP) && n->vdev.vm_running; +} + +static void virtio_net_vhost_status(VirtIONet *n, uint8_t status) +{ + if (!n->nic->nc.peer) { + return; + } + if (n->nic->nc.peer->info->type != NET_CLIENT_TYPE_TAP) { + return; + } + + if (!tap_get_vhost_net(n->nic->nc.peer)) { + return; + } + if (!!n->vhost_started == virtio_net_started(n, status)) { + return; + } + if (!n->vhost_started) { + int r = vhost_net_start(tap_get_vhost_net(n->nic->nc.peer), &n->vdev); + if (r < 0) { + error_report("unable to start vhost net: %d: " + "falling back on userspace virtio", -r); + } else { + n->vhost_started = 1; + } + } else { + vhost_net_stop(tap_get_vhost_net(n->nic->nc.peer), &n->vdev); + n->vhost_started = 0; + } +} + +static void virtio_net_set_status(struct VirtIODevice *vdev, uint8_t status) +{ + VirtIONet *n = to_virtio_net(vdev); + + virtio_net_vhost_status(n, status); + + if (!n->tx_waiting) { + return; + } + + if (virtio_net_started(n, status) && !n->vhost_started) { + if (n->tx_timer) { + qemu_mod_timer(n->tx_timer, + qemu_get_clock(vm_clock) + n->tx_timeout); + } else { + qemu_bh_schedule(n->tx_bh); + } + } else { + if (n->tx_timer) { + qemu_del_timer(n->tx_timer); + } else { + qemu_bh_cancel(n->tx_bh); + } + } +} + static void virtio_net_set_link_status(VLANClientState *nc) { VirtIONet *n = DO_UPCAST(NICState, nc, nc)->opaque; @@ -105,6 +170,8 @@ static void virtio_net_set_link_status(VLANClientState *nc) if (n->status != old_status) virtio_notify_config(&n->vdev); + + virtio_net_set_status(&n->vdev, n->vdev.status); } static void virtio_net_reset(VirtIODevice *vdev) @@ -118,10 +185,6 @@ static void virtio_net_reset(VirtIODevice *vdev) n->nomulti = 0; n->nouni = 0; n->nobcast = 0; - if (n->vhost_started) { - vhost_net_stop(tap_get_vhost_net(n->nic->nc.peer), vdev); - n->vhost_started = 0; - } /* Flush any MAC and VLAN filter table state */ n->mac_table.in_use = 0; @@ -226,7 +289,7 @@ static void virtio_net_set_features(VirtIODevice *vdev, uint32_t features) if (!tap_get_vhost_net(n->nic->nc.peer)) { return; } - return vhost_net_ack_features(tap_get_vhost_net(n->nic->nc.peer), features); + vhost_net_ack_features(tap_get_vhost_net(n->nic->nc.peer), features); } static int virtio_net_handle_rx_mode(VirtIONet *n, uint8_t cmd, @@ -235,7 +298,7 @@ static int virtio_net_handle_rx_mode(VirtIONet *n, uint8_t cmd, uint8_t on; if (elem->out_num != 2 || elem->out_sg[1].iov_len != sizeof(on)) { - fprintf(stderr, "virtio-net ctrl invalid rx mode command\n"); + error_report("virtio-net ctrl invalid rx mode command"); exit(1); } @@ -317,7 +380,7 @@ static int virtio_net_handle_vlan_table(VirtIONet *n, uint8_t cmd, uint16_t vid; if (elem->out_num != 2 || elem->out_sg[1].iov_len != sizeof(vid)) { - fprintf(stderr, "virtio-net ctrl invalid vlan command\n"); + error_report("virtio-net ctrl invalid vlan command"); return VIRTIO_NET_ERR; } @@ -345,13 +408,13 @@ static void virtio_net_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) while (virtqueue_pop(vq, &elem)) { if ((elem.in_num < 1) || (elem.out_num < 1)) { - fprintf(stderr, "virtio-net ctrl missing headers\n"); + error_report("virtio-net ctrl missing headers"); exit(1); } if (elem.out_sg[0].iov_len < sizeof(ctrl) || elem.in_sg[elem.in_num - 1].iov_len < sizeof(status)) { - fprintf(stderr, "virtio-net ctrl header not in correct element\n"); + error_report("virtio-net ctrl header not in correct element"); exit(1); } @@ -388,6 +451,9 @@ static void virtio_net_handle_rx(VirtIODevice *vdev, VirtQueue *vq) static int virtio_net_can_receive(VLANClientState *nc) { VirtIONet *n = DO_UPCAST(NICState, nc, nc)->opaque; + if (!n->vdev.vm_running) { + return 0; + } if (!virtio_queue_ready(n->rx_vq) || !(n->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK)) @@ -445,21 +511,6 @@ static void work_around_broken_dhclient(struct virtio_net_hdr *hdr, } } -static int iov_fill(struct iovec *iov, int iovcnt, const void *buf, int count) -{ - int offset, i; - - offset = i = 0; - while (offset < count && i < iovcnt) { - int len = MIN(iov[i].iov_len, count - offset); - memcpy(iov[i].iov_base, buf + offset, len); - offset += len; - i++; - } - - return offset; -} - static int receive_header(VirtIONet *n, struct iovec *iov, int iovcnt, const void *buf, size_t size, size_t hdr_len) { @@ -541,21 +592,23 @@ static ssize_t virtio_net_receive(VLANClientState *nc, const uint8_t *buf, size_ { VirtIONet *n = DO_UPCAST(NICState, nc, nc)->opaque; struct virtio_net_hdr_mrg_rxbuf *mhdr = NULL; - size_t hdr_len, offset, i; + size_t guest_hdr_len, offset, i, host_hdr_len; if (!virtio_net_can_receive(&n->nic->nc)) return -1; - if (!virtio_net_has_buffers(n, size)) + /* 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)) return 0; if (!receive_filter(n, buf, size)) return size; - /* hdr_len refers to the header we supply to the guest */ - hdr_len = n->mergeable_rx_bufs ? - sizeof(struct virtio_net_hdr_mrg_rxbuf) : sizeof(struct virtio_net_hdr); - offset = i = 0; while (offset < size) { @@ -565,21 +618,24 @@ static ssize_t virtio_net_receive(VLANClientState *nc, const uint8_t *buf, size_ total = 0; - if ((i != 0 && !n->mergeable_rx_bufs) || - virtqueue_pop(n->rx_vq, &elem) == 0) { + if (virtqueue_pop(n->rx_vq, &elem) == 0) { if (i == 0) return -1; - fprintf(stderr, "virtio-net truncating packet\n"); + 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%x", + i, n->mergeable_rx_bufs, offset, size, + guest_hdr_len, host_hdr_len, n->vdev.guest_features); exit(1); } if (elem.in_num < 1) { - fprintf(stderr, "virtio-net receive queue contains no in buffers\n"); + error_report("virtio-net receive queue contains no in buffers"); exit(1); } - if (!n->mergeable_rx_bufs && elem.in_sg[0].iov_len != hdr_len) { - fprintf(stderr, "virtio-net header not in first element\n"); + 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); } @@ -590,19 +646,31 @@ static ssize_t virtio_net_receive(VLANClientState *nc, const uint8_t *buf, size_ mhdr = (struct virtio_net_hdr_mrg_rxbuf *)sg[0].iov_base; offset += receive_header(n, sg, elem.in_num, - buf + offset, size - offset, hdr_len); - total += hdr_len; + buf + offset, size - offset, guest_hdr_len); + total += guest_hdr_len; } /* copy in packet. ugh */ - len = iov_fill(sg, elem.in_num, - buf + offset, size - offset); + len = iov_from_buf(sg, elem.in_num, + 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) { +#if 0 + error_report("virtio-net truncated non-mergeable packet: " + "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); +#endif + return size; + } /* signal other side */ virtqueue_fill(n->rx_vq, &elem, total, i++); - - offset += len; } if (mhdr) @@ -614,7 +682,7 @@ static ssize_t virtio_net_receive(VLANClientState *nc, const uint8_t *buf, size_ return size; } -static void virtio_net_flush_tx(VirtIONet *n, VirtQueue *vq); +static int32_t virtio_net_flush_tx(VirtIONet *n, VirtQueue *vq); static void virtio_net_tx_complete(VLANClientState *nc, ssize_t len) { @@ -630,16 +698,19 @@ static void virtio_net_tx_complete(VLANClientState *nc, ssize_t len) } /* TX */ -static void virtio_net_flush_tx(VirtIONet *n, VirtQueue *vq) +static int32_t virtio_net_flush_tx(VirtIONet *n, VirtQueue *vq) { VirtQueueElement elem; + int32_t num_packets = 0; + if (!(n->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK)) { + return num_packets; + } - if (!(n->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK)) - return; + assert(n->vdev.vm_running); if (n->async_tx.elem.out_num) { virtio_queue_set_notification(n->tx_vq, 0); - return; + return num_packets; } while (virtqueue_pop(vq, &elem)) { @@ -654,7 +725,7 @@ static void virtio_net_flush_tx(VirtIONet *n, VirtQueue *vq) sizeof(struct virtio_net_hdr); if (out_num < 1 || out_sg->iov_len != hdr_len) { - fprintf(stderr, "virtio-net header not in first element\n"); + error_report("virtio-net header not in first element"); exit(1); } @@ -676,38 +747,66 @@ static void virtio_net_flush_tx(VirtIONet *n, VirtQueue *vq) virtio_queue_set_notification(n->tx_vq, 0); n->async_tx.elem = elem; n->async_tx.len = len; - return; + return -EBUSY; } len += ret; virtqueue_push(vq, &elem, len); virtio_notify(&n->vdev, vq); + + if (++num_packets >= n->tx_burst) { + break; + } } + return num_packets; } -static void virtio_net_handle_tx(VirtIODevice *vdev, VirtQueue *vq) +static void virtio_net_handle_tx_timer(VirtIODevice *vdev, VirtQueue *vq) { VirtIONet *n = to_virtio_net(vdev); - if (n->tx_timer_active) { + /* This happens when device was stopped but VCPU wasn't. */ + if (!n->vdev.vm_running) { + n->tx_waiting = 1; + return; + } + + if (n->tx_waiting) { virtio_queue_set_notification(vq, 1); qemu_del_timer(n->tx_timer); - n->tx_timer_active = 0; + n->tx_waiting = 0; virtio_net_flush_tx(n, vq); } else { qemu_mod_timer(n->tx_timer, - qemu_get_clock(vm_clock) + TX_TIMER_INTERVAL); - n->tx_timer_active = 1; + qemu_get_clock(vm_clock) + n->tx_timeout); + n->tx_waiting = 1; virtio_queue_set_notification(vq, 0); } } +static void virtio_net_handle_tx_bh(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIONet *n = to_virtio_net(vdev); + + if (unlikely(n->tx_waiting)) { + return; + } + n->tx_waiting = 1; + /* This happens when device was stopped but VCPU wasn't. */ + if (!n->vdev.vm_running) { + return; + } + virtio_queue_set_notification(vq, 0); + qemu_bh_schedule(n->tx_bh); +} + static void virtio_net_tx_timer(void *opaque) { VirtIONet *n = opaque; + assert(n->vdev.vm_running); - n->tx_timer_active = 0; + n->tx_waiting = 0; /* Just in case the driver is not ready on more */ if (!(n->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK)) @@ -717,20 +816,54 @@ static void virtio_net_tx_timer(void *opaque) virtio_net_flush_tx(n, n->tx_vq); } -static void virtio_net_save(QEMUFile *f, void *opaque) +static void virtio_net_tx_bh(void *opaque) { VirtIONet *n = opaque; + int32_t ret; - if (n->vhost_started) { - /* TODO: should we really stop the backend? - * If we don't, it might keep writing to memory. */ - vhost_net_stop(tap_get_vhost_net(n->nic->nc.peer), &n->vdev); - n->vhost_started = 0; + assert(n->vdev.vm_running); + + n->tx_waiting = 0; + + /* Just in case the driver is not ready on more */ + if (unlikely(!(n->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK))) + return; + + ret = virtio_net_flush_tx(n, n->tx_vq); + if (ret == -EBUSY) { + return; /* Notification re-enable handled by tx_complete */ } + + /* If we flush a full burst of packets, assume there are + * more coming and immediately reschedule */ + if (ret >= n->tx_burst) { + qemu_bh_schedule(n->tx_bh); + n->tx_waiting = 1; + return; + } + + /* If less than a full burst, re-enable notification and flush + * anything that may have come in while we weren't looking. If + * we find something, assume the guest is still active and reschedule */ + virtio_queue_set_notification(n->tx_vq, 1); + if (virtio_net_flush_tx(n, n->tx_vq) > 0) { + virtio_queue_set_notification(n->tx_vq, 0); + qemu_bh_schedule(n->tx_bh); + n->tx_waiting = 1; + } +} + +static void virtio_net_save(QEMUFile *f, void *opaque) +{ + VirtIONet *n = opaque; + + /* At this point, backend must be stopped, otherwise + * it might keep writing to memory. */ + assert(!n->vhost_started); virtio_save(&n->vdev, f); qemu_put_buffer(f, n->mac, ETH_ALEN); - qemu_put_be32(f, n->tx_timer_active); + qemu_put_be32(f, n->tx_waiting); qemu_put_be32(f, n->mergeable_rx_bufs); qemu_put_be16(f, n->status); qemu_put_byte(f, n->promisc); @@ -759,7 +892,7 @@ static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) virtio_load(&n->vdev, f); qemu_get_buffer(f, n->mac, ETH_ALEN); - n->tx_timer_active = qemu_get_be32(f); + n->tx_waiting = qemu_get_be32(f); n->mergeable_rx_bufs = qemu_get_be32(f); if (version_id >= 3) @@ -834,11 +967,6 @@ static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) } } n->mac_table.first_multi = i; - - if (n->tx_timer_active) { - qemu_mod_timer(n->tx_timer, - qemu_get_clock(vm_clock) + TX_TIMER_INTERVAL); - } return 0; } @@ -858,51 +986,10 @@ static NetClientInfo net_virtio_info = { .link_status_changed = virtio_net_set_link_status, }; -static void virtio_net_set_status(struct VirtIODevice *vdev, uint8_t status) -{ - VirtIONet *n = to_virtio_net(vdev); - if (!n->nic->nc.peer) { - return; - } - if (n->nic->nc.peer->info->type != NET_CLIENT_TYPE_TAP) { - return; - } - - if (!tap_get_vhost_net(n->nic->nc.peer)) { - return; - } - if (!!n->vhost_started == !!(status & VIRTIO_CONFIG_S_DRIVER_OK)) { - return; - } - if (status & VIRTIO_CONFIG_S_DRIVER_OK) { - int r = vhost_net_start(tap_get_vhost_net(n->nic->nc.peer), vdev); - if (r < 0) { - fprintf(stderr, "unable to start vhost net: %d: " - "falling back on userspace virtio\n", -r); - } else { - n->vhost_started = 1; - } - } else { - vhost_net_stop(tap_get_vhost_net(n->nic->nc.peer), vdev); - n->vhost_started = 0; - } -} - -static void virtio_net_vmstate_change(void *opaque, int running, int reason) -{ - VirtIONet *n = opaque; - if (!running) { - return; - } - /* This is called when vm is started, it will start vhost backend if - * appropriate e.g. after migration. */ - virtio_net_set_status(&n->vdev, n->vdev.status); -} - -VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf) +VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf, + virtio_net_conf *net) { VirtIONet *n; - static int virtio_net_id; n = (VirtIONet *)virtio_common_init("virtio-net", VIRTIO_ID_NET, sizeof(struct virtio_net_config), @@ -916,7 +1003,22 @@ VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf) n->vdev.reset = virtio_net_reset; n->vdev.set_status = virtio_net_set_status; n->rx_vq = virtio_add_queue(&n->vdev, 256, virtio_net_handle_rx); - n->tx_vq = virtio_add_queue(&n->vdev, 256, virtio_net_handle_tx); + + if (net->tx && strcmp(net->tx, "timer") && strcmp(net->tx, "bh")) { + error_report("virtio-net: " + "Unknown option tx=%s, valid options: \"timer\" \"bh\"", + net->tx); + error_report("Defaulting to \"bh\""); + } + + if (net->tx && !strcmp(net->tx, "timer")) { + n->tx_vq = virtio_add_queue(&n->vdev, 256, virtio_net_handle_tx_timer); + n->tx_timer = qemu_new_timer(vm_clock, virtio_net_tx_timer, n); + n->tx_timeout = net->txtimer; + } else { + n->tx_vq = virtio_add_queue(&n->vdev, 256, virtio_net_handle_tx_bh); + n->tx_bh = qemu_bh_new(virtio_net_tx_bh, n); + } n->ctrl_vq = virtio_add_queue(&n->vdev, 64, virtio_net_handle_ctrl); qemu_macaddr_default_if_unset(&conf->macaddr); memcpy(&n->mac[0], &conf->macaddr, sizeof(n->mac)); @@ -926,8 +1028,8 @@ VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf) qemu_format_nic_info_str(&n->nic->nc, conf->macaddr.a); - n->tx_timer = qemu_new_timer(vm_clock, virtio_net_tx_timer, n); - n->tx_timer_active = 0; + n->tx_waiting = 0; + n->tx_burst = net->txburst; n->mergeable_rx_bufs = 0; n->promisc = 1; /* for compatibility */ @@ -935,9 +1037,11 @@ VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf) n->vlans = qemu_mallocz(MAX_VLAN >> 3); - register_savevm("virtio-net", virtio_net_id++, VIRTIO_NET_VM_VERSION, + n->qdev = dev; + register_savevm(dev, "virtio-net", -1, VIRTIO_NET_VM_VERSION, virtio_net_save, virtio_net_load, n); - n->vmstate = qemu_add_vm_change_state_handler(virtio_net_vmstate_change, n); + + add_boot_device_path(conf->bootindex, dev, "/ethernet-phy@0"); return &n->vdev; } @@ -945,21 +1049,23 @@ VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf) void virtio_net_exit(VirtIODevice *vdev) { VirtIONet *n = DO_UPCAST(VirtIONet, vdev, vdev); - qemu_del_vm_change_state_handler(n->vmstate); - if (n->vhost_started) { - vhost_net_stop(tap_get_vhost_net(n->nic->nc.peer), vdev); - } + /* This will stop vhost backend if appropriate. */ + virtio_net_set_status(vdev, 0); qemu_purge_queued_packets(&n->nic->nc); - unregister_savevm("virtio-net", n); + unregister_savevm(n->qdev, "virtio-net", n); qemu_free(n->mac_table.macs); qemu_free(n->vlans); - qemu_del_timer(n->tx_timer); - qemu_free_timer(n->tx_timer); + if (n->tx_timer) { + qemu_del_timer(n->tx_timer); + qemu_free_timer(n->tx_timer); + } else { + qemu_bh_delete(n->tx_bh); + } virtio_cleanup(&n->vdev); qemu_del_vlan_client(&n->nic->nc);