diff options
Diffstat (limited to 'hw/virtio-net.c')
-rw-r--r-- | hw/virtio-net.c | 154 |
1 files changed, 121 insertions, 33 deletions
diff --git a/hw/virtio-net.c b/hw/virtio-net.c index 60aa6dab1b..d584287a51 100644 --- a/hw/virtio-net.c +++ b/hw/virtio-net.c @@ -16,9 +16,9 @@ #include "qemu-timer.h" #include "virtio-net.h" -#define VIRTIO_NET_VM_VERSION 6 +#define VIRTIO_NET_VM_VERSION 10 -#define MAC_TABLE_ENTRIES 32 +#define MAC_TABLE_ENTRIES 64 #define MAX_VLAN (1 << 12) /* Per 802.1Q definition */ typedef struct VirtIONet @@ -33,10 +33,17 @@ typedef struct VirtIONet QEMUTimer *tx_timer; int tx_timer_active; int mergeable_rx_bufs; - int promisc; - int allmulti; + uint8_t promisc; + uint8_t allmulti; + uint8_t alluni; + uint8_t nomulti; + uint8_t nouni; + uint8_t nobcast; struct { int in_use; + int first_multi; + uint8_t multi_overflow; + uint8_t uni_overflow; uint8_t *macs; } mac_table; uint32_t *vlans; @@ -95,9 +102,16 @@ static void virtio_net_reset(VirtIODevice *vdev) /* Reset back to compatibility mode */ n->promisc = 1; n->allmulti = 0; + n->alluni = 0; + n->nomulti = 0; + n->nouni = 0; + n->nobcast = 0; /* Flush any MAC and VLAN filter table state */ n->mac_table.in_use = 0; + n->mac_table.first_multi = 0; + n->mac_table.multi_overflow = 0; + n->mac_table.uni_overflow = 0; memset(n->mac_table.macs, 0, MAC_TABLE_ENTRIES * ETH_ALEN); memset(n->vlans, 0, MAX_VLAN >> 3); } @@ -108,7 +122,8 @@ static uint32_t virtio_net_get_features(VirtIODevice *vdev) (1 << VIRTIO_NET_F_STATUS) | (1 << VIRTIO_NET_F_CTRL_VQ) | (1 << VIRTIO_NET_F_CTRL_RX) | - (1 << VIRTIO_NET_F_CTRL_VLAN); + (1 << VIRTIO_NET_F_CTRL_VLAN) | + (1 << VIRTIO_NET_F_CTRL_RX_EXTRA); return features; } @@ -151,6 +166,14 @@ static int virtio_net_handle_rx_mode(VirtIONet *n, uint8_t cmd, n->promisc = on; else if (cmd == VIRTIO_NET_CTRL_RX_MODE_ALLMULTI) n->allmulti = on; + else if (cmd == VIRTIO_NET_CTRL_RX_MODE_ALLUNI) + n->alluni = on; + else if (cmd == VIRTIO_NET_CTRL_RX_MODE_NOMULTI) + n->nomulti = on; + else if (cmd == VIRTIO_NET_CTRL_RX_MODE_NOUNI) + n->nouni = on; + else if (cmd == VIRTIO_NET_CTRL_RX_MODE_NOBCAST) + n->nobcast = on; else return VIRTIO_NET_ERR; @@ -168,6 +191,9 @@ static int virtio_net_handle_mac(VirtIONet *n, uint8_t cmd, return VIRTIO_NET_ERR; n->mac_table.in_use = 0; + n->mac_table.first_multi = 0; + n->mac_table.uni_overflow = 0; + n->mac_table.multi_overflow = 0; memset(n->mac_table.macs, 0, MAC_TABLE_ENTRIES * ETH_ALEN); mac_data.entries = ldl_le_p(elem->out_sg[1].iov_base); @@ -181,10 +207,11 @@ static int virtio_net_handle_mac(VirtIONet *n, uint8_t cmd, mac_data.entries * ETH_ALEN); n->mac_table.in_use += mac_data.entries; } else { - n->promisc = 1; - return VIRTIO_NET_OK; + n->mac_table.uni_overflow = 1; } + n->mac_table.first_multi = n->mac_table.in_use; + mac_data.entries = ldl_le_p(elem->out_sg[2].iov_base); if (sizeof(mac_data.entries) + @@ -197,8 +224,9 @@ static int virtio_net_handle_mac(VirtIONet *n, uint8_t cmd, elem->out_sg[2].iov_base + sizeof(mac_data), mac_data.entries * ETH_ALEN); n->mac_table.in_use += mac_data.entries; - } else - n->allmulti = 1; + } else { + n->mac_table.multi_overflow = 1; + } } return VIRTIO_NET_OK; @@ -269,6 +297,9 @@ static void virtio_net_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) static void virtio_net_handle_rx(VirtIODevice *vdev, VirtQueue *vq) { + VirtIONet *n = to_virtio_net(vdev); + + qemu_flush_queued_packets(n->vc); } static int do_virtio_net_can_receive(VirtIONet *n, int bufsize) @@ -288,9 +319,9 @@ static int do_virtio_net_can_receive(VirtIONet *n, int bufsize) return 1; } -static int virtio_net_can_receive(void *opaque) +static int virtio_net_can_receive(VLANClientState *vc) { - VirtIONet *n = opaque; + VirtIONet *n = vc->opaque; return do_virtio_net_can_receive(n, VIRTIO_NET_MAX_BUFSIZE); } @@ -344,34 +375,50 @@ static int receive_filter(VirtIONet *n, const uint8_t *buf, int size) return 0; } - if ((ptr[0] & 1) && n->allmulti) - return 1; - - if (!memcmp(ptr, bcast, sizeof(bcast))) - return 1; - - if (!memcmp(ptr, n->mac, ETH_ALEN)) - return 1; + if (ptr[0] & 1) { // multicast + if (!memcmp(ptr, bcast, sizeof(bcast))) { + return !n->nobcast; + } else if (n->nomulti) { + return 0; + } else if (n->allmulti || n->mac_table.multi_overflow) { + return 1; + } - for (i = 0; i < n->mac_table.in_use; i++) { - if (!memcmp(ptr, &n->mac_table.macs[i * ETH_ALEN], ETH_ALEN)) + for (i = n->mac_table.first_multi; i < n->mac_table.in_use; i++) { + if (!memcmp(ptr, &n->mac_table.macs[i * ETH_ALEN], ETH_ALEN)) { + return 1; + } + } + } else { // unicast + if (n->nouni) { + return 0; + } else if (n->alluni || n->mac_table.uni_overflow) { + return 1; + } else if (!memcmp(ptr, n->mac, ETH_ALEN)) { return 1; + } + + for (i = 0; i < n->mac_table.first_multi; i++) { + if (!memcmp(ptr, &n->mac_table.macs[i * ETH_ALEN], ETH_ALEN)) { + return 1; + } + } } return 0; } -static void virtio_net_receive(void *opaque, const uint8_t *buf, int size) +static ssize_t virtio_net_receive(VLANClientState *vc, const uint8_t *buf, size_t size) { - VirtIONet *n = opaque; + VirtIONet *n = vc->opaque; struct virtio_net_hdr_mrg_rxbuf *mhdr = NULL; size_t hdr_len, offset, i; if (!do_virtio_net_can_receive(n, size)) - return; + return 0; if (!receive_filter(n, buf, size)) - return; + return size; /* hdr_len refers to the header we supply to the guest */ hdr_len = n->mergeable_rx_bufs ? @@ -389,7 +436,7 @@ static void virtio_net_receive(void *opaque, const uint8_t *buf, int size) if ((i != 0 && !n->mergeable_rx_bufs) || virtqueue_pop(n->rx_vq, &elem) == 0) { if (i == 0) - return; + return -1; fprintf(stderr, "virtio-net truncating packet\n"); exit(1); } @@ -431,6 +478,8 @@ static void virtio_net_receive(void *opaque, const uint8_t *buf, int size) virtqueue_flush(n->rx_vq, i); virtio_notify(&n->vdev, n->rx_vq); + + return size; } /* TX */ @@ -518,16 +567,24 @@ static void virtio_net_save(QEMUFile *f, void *opaque) qemu_put_be32(f, n->tx_timer_active); qemu_put_be32(f, n->mergeable_rx_bufs); qemu_put_be16(f, n->status); - qemu_put_be32(f, n->promisc); - qemu_put_be32(f, n->allmulti); + qemu_put_byte(f, n->promisc); + qemu_put_byte(f, n->allmulti); qemu_put_be32(f, n->mac_table.in_use); qemu_put_buffer(f, n->mac_table.macs, n->mac_table.in_use * ETH_ALEN); qemu_put_buffer(f, (uint8_t *)n->vlans, MAX_VLAN >> 3); + qemu_put_be32(f, 0); /* vnet-hdr placeholder */ + qemu_put_byte(f, n->mac_table.multi_overflow); + qemu_put_byte(f, n->mac_table.uni_overflow); + qemu_put_byte(f, n->alluni); + qemu_put_byte(f, n->nomulti); + qemu_put_byte(f, n->nouni); + qemu_put_byte(f, n->nobcast); } static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) { VirtIONet *n = opaque; + int i; if (version_id < 2 || version_id > VIRTIO_NET_VM_VERSION) return -EINVAL; @@ -542,8 +599,13 @@ static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) n->status = qemu_get_be16(f); if (version_id >= 4) { - n->promisc = qemu_get_be32(f); - n->allmulti = qemu_get_be32(f); + if (version_id < 8) { + n->promisc = qemu_get_be32(f); + n->allmulti = qemu_get_be32(f); + } else { + n->promisc = qemu_get_byte(f); + n->allmulti = qemu_get_byte(f); + } } if (version_id >= 5) { @@ -554,7 +616,7 @@ static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) n->mac_table.in_use * ETH_ALEN); } else if (n->mac_table.in_use) { qemu_fseek(f, n->mac_table.in_use * ETH_ALEN, SEEK_CUR); - n->promisc = 1; + n->mac_table.multi_overflow = n->mac_table.uni_overflow = 1; n->mac_table.in_use = 0; } } @@ -562,6 +624,32 @@ static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) if (version_id >= 6) qemu_get_buffer(f, (uint8_t *)n->vlans, MAX_VLAN >> 3); + if (version_id >= 7 && qemu_get_be32(f)) { + fprintf(stderr, + "virtio-net: saved image requires vnet header support\n"); + exit(1); + } + + if (version_id >= 9) { + n->mac_table.multi_overflow = qemu_get_byte(f); + n->mac_table.uni_overflow = qemu_get_byte(f); + } + + if (version_id >= 10) { + n->alluni = qemu_get_byte(f); + n->nomulti = qemu_get_byte(f); + n->nouni = qemu_get_byte(f); + n->nobcast = qemu_get_byte(f); + } + + /* Find the first multicast entry in the saved MAC filter */ + for (i = 0; i < n->mac_table.in_use; i++) { + if (n->mac_table.macs[i * ETH_ALEN] & 1) { + break; + } + } + 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); @@ -602,12 +690,12 @@ VirtIODevice *virtio_net_init(DeviceState *dev) n->vdev.reset = virtio_net_reset; 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); - n->ctrl_vq = virtio_add_queue(&n->vdev, 16, virtio_net_handle_ctrl); + n->ctrl_vq = virtio_add_queue(&n->vdev, 64, virtio_net_handle_ctrl); qdev_get_macaddr(dev, n->mac); n->status = VIRTIO_NET_S_LINK_UP; n->vc = qdev_get_vlan_client(dev, - virtio_net_receive, virtio_net_can_receive, + virtio_net_receive, NULL, virtio_net_cleanup, n); n->vc->link_status_changed = virtio_net_set_link_status; |