Merge remote-tracking branch 'stefanha/net' into staging

# By Jason Wang (2) and others
# Via Stefan Hajnoczi
* stefanha/net:
  qmp: netdev_add is like -netdev, not -net, fix documentation
  doc: document -netdev hubport
  net: reduce the unnecessary memory allocation of multiqueue
  tap: set IFF_ONE_QUEUE per default
  tap: forbid creating multiqueue tap when hub is used
  net: fix unbounded NetQueue
  net: fix qemu_flush_queued_packets() in presence of a hub
diff --git a/hmp-commands.hx b/hmp-commands.hx
index cef7708..69c707d 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1169,7 +1169,7 @@
     {
         .name       = "netdev_add",
         .args_type  = "netdev:O",
-        .params     = "[user|tap|socket],id=str[,prop=value][,...]",
+        .params     = "[user|tap|socket|hubport],id=str[,prop=value][,...]",
         .help       = "add host network device",
         .mhandler.cmd = hmp_netdev_add,
     },
diff --git a/hw/virtio-net.c b/hw/virtio-net.c
index 573c669..bb2c26c 100644
--- a/hw/virtio-net.c
+++ b/hw/virtio-net.c
@@ -44,7 +44,7 @@
     VirtIODevice vdev;
     uint8_t mac[ETH_ALEN];
     uint16_t status;
-    VirtIONetQueue vqs[MAX_QUEUE_NUM];
+    VirtIONetQueue *vqs;
     VirtQueue *ctrl_vq;
     NICState *nic;
     uint32_t tx_timeout;
@@ -1326,8 +1326,9 @@
     n->vdev.set_status = virtio_net_set_status;
     n->vdev.guest_notifier_mask = virtio_net_guest_notifier_mask;
     n->vdev.guest_notifier_pending = virtio_net_guest_notifier_pending;
+    n->max_queues = MAX(conf->queues, 1);
+    n->vqs = g_malloc0(sizeof(VirtIONetQueue) * n->max_queues);
     n->vqs[0].rx_vq = virtio_add_queue(&n->vdev, 256, virtio_net_handle_rx);
-    n->max_queues = conf->queues;
     n->curr_queues = 1;
     n->vqs[0].n = n;
     n->tx_timeout = net->txtimer;
@@ -1412,6 +1413,7 @@
         }
     }
 
+    g_free(n->vqs);
     qemu_del_nic(n->nic);
     virtio_cleanup(&n->vdev);
 }
diff --git a/include/net/net.h b/include/net/net.h
index 43a045e..cb049a1 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -72,7 +72,7 @@
 };
 
 typedef struct NICState {
-    NetClientState ncs[MAX_QUEUE_NUM];
+    NetClientState *ncs;
     NICConf *conf;
     void *opaque;
     bool peer_deleted;
diff --git a/net/hub.c b/net/hub.c
index a24c9d1..df32074 100644
--- a/net/hub.c
+++ b/net/hub.c
@@ -338,3 +338,17 @@
         }
     }
 }
+
+bool net_hub_flush(NetClientState *nc)
+{
+    NetHubPort *port;
+    NetHubPort *source_port = DO_UPCAST(NetHubPort, nc, nc);
+    int ret = 0;
+
+    QLIST_FOREACH(port, &source_port->hub->ports, next) {
+        if (port != source_port) {
+            ret += qemu_net_queue_flush(port->nc.send_queue);
+        }
+    }
+    return ret ? true : false;
+}
diff --git a/net/hub.h b/net/hub.h
index 583ada8..a625eff 100644
--- a/net/hub.h
+++ b/net/hub.h
@@ -21,5 +21,6 @@
 NetClientState *net_hub_find_client_by_name(int hub_id, const char *name);
 void net_hub_info(Monitor *mon);
 void net_hub_check_clients(void);
+bool net_hub_flush(NetClientState *nc);
 
 #endif /* NET_HUB_H */
diff --git a/net/net.c b/net/net.c
index be03a8d..f3d67f8 100644
--- a/net/net.c
+++ b/net/net.c
@@ -235,23 +235,20 @@
                        const char *name,
                        void *opaque)
 {
-    NetClientState *nc;
     NetClientState **peers = conf->peers.ncs;
     NICState *nic;
-    int i;
+    int i, queues = MAX(1, conf->queues);
 
     assert(info->type == NET_CLIENT_OPTIONS_KIND_NIC);
     assert(info->size >= sizeof(NICState));
 
-    nc = qemu_new_net_client(info, peers[0], model, name);
-    nc->queue_index = 0;
-
-    nic = qemu_get_nic(nc);
+    nic = g_malloc0(info->size + sizeof(NetClientState) * queues);
+    nic->ncs = (void *)nic + info->size;
     nic->conf = conf;
     nic->opaque = opaque;
 
-    for (i = 1; i < conf->queues; i++) {
-        qemu_net_client_setup(&nic->ncs[i], info, peers[i], model, nc->name,
+    for (i = 0; i < queues; i++) {
+        qemu_net_client_setup(&nic->ncs[i], info, peers[i], model, name,
                               NULL);
         nic->ncs[i].queue_index = i;
     }
@@ -261,7 +258,7 @@
 
 NetClientState *qemu_get_subqueue(NICState *nic, int queue_index)
 {
-    return &nic->ncs[queue_index];
+    return nic->ncs + queue_index;
 }
 
 NetClientState *qemu_get_queue(NICState *nic)
@@ -273,7 +270,7 @@
 {
     NetClientState *nc0 = nc - nc->queue_index;
 
-    return DO_UPCAST(NICState, ncs[0], nc0);
+    return (NICState *)((void *)nc0 - nc->info->size);
 }
 
 void *qemu_get_nic_opaque(NetClientState *nc)
@@ -368,6 +365,8 @@
         qemu_cleanup_net_client(nc);
         qemu_free_net_client(nc);
     }
+
+    g_free(nic);
 }
 
 void qemu_foreach_nic(qemu_nic_foreach func, void *opaque)
@@ -441,6 +440,12 @@
 {
     nc->receive_disabled = 0;
 
+    if (nc->peer && nc->peer->info->type == NET_CLIENT_OPTIONS_KIND_HUBPORT) {
+        if (net_hub_flush(nc->peer)) {
+            qemu_notify_event();
+        }
+        return;
+    }
     if (qemu_net_queue_flush(nc->send_queue)) {
         /* We emptied the queue successfully, signal to the IO thread to repoll
          * the file descriptor (for tap, for example).
diff --git a/net/queue.c b/net/queue.c
index 6eaf5b6..859d02a 100644
--- a/net/queue.c
+++ b/net/queue.c
@@ -50,6 +50,8 @@
 
 struct NetQueue {
     void *opaque;
+    uint32_t nq_maxlen;
+    uint32_t nq_count;
 
     QTAILQ_HEAD(packets, NetPacket) packets;
 
@@ -63,6 +65,8 @@
     queue = g_malloc0(sizeof(NetQueue));
 
     queue->opaque = opaque;
+    queue->nq_maxlen = 10000;
+    queue->nq_count = 0;
 
     QTAILQ_INIT(&queue->packets);
 
@@ -92,6 +96,9 @@
 {
     NetPacket *packet;
 
+    if (queue->nq_count >= queue->nq_maxlen && !sent_cb) {
+        return; /* drop if queue full and no callback */
+    }
     packet = g_malloc(sizeof(NetPacket) + size);
     packet->sender = sender;
     packet->flags = flags;
@@ -99,6 +106,7 @@
     packet->sent_cb = sent_cb;
     memcpy(packet->data, buf, size);
 
+    queue->nq_count++;
     QTAILQ_INSERT_TAIL(&queue->packets, packet, entry);
 }
 
@@ -113,6 +121,9 @@
     size_t max_len = 0;
     int i;
 
+    if (queue->nq_count >= queue->nq_maxlen && !sent_cb) {
+        return; /* drop if queue full and no callback */
+    }
     for (i = 0; i < iovcnt; i++) {
         max_len += iov[i].iov_len;
     }
@@ -130,6 +141,7 @@
         packet->size += len;
     }
 
+    queue->nq_count++;
     QTAILQ_INSERT_TAIL(&queue->packets, packet, entry);
 }
 
@@ -220,6 +232,7 @@
     QTAILQ_FOREACH_SAFE(packet, &queue->packets, entry, next) {
         if (packet->sender == from) {
             QTAILQ_REMOVE(&queue->packets, packet, entry);
+            queue->nq_count--;
             g_free(packet);
         }
     }
@@ -233,6 +246,7 @@
 
         packet = QTAILQ_FIRST(&queue->packets);
         QTAILQ_REMOVE(&queue->packets, packet, entry);
+        queue->nq_count--;
 
         ret = qemu_net_queue_deliver(queue,
                                      packet->sender,
@@ -240,6 +254,7 @@
                                      packet->data,
                                      packet->size);
         if (ret == 0) {
+            queue->nq_count++;
             QTAILQ_INSERT_HEAD(&queue->packets, packet, entry);
             return false;
         }
diff --git a/net/tap-linux.c b/net/tap-linux.c
index a953189..36c09e2 100644
--- a/net/tap-linux.c
+++ b/net/tap-linux.c
@@ -42,6 +42,7 @@
     struct ifreq ifr;
     int fd, ret;
     int len = sizeof(struct virtio_net_hdr);
+    unsigned int features;
 
     TFR(fd = open(PATH_NET_TUN, O_RDWR));
     if (fd < 0) {
@@ -51,9 +52,12 @@
     memset(&ifr, 0, sizeof(ifr));
     ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
 
-    if (*vnet_hdr) {
-        unsigned int features;
+    if (ioctl(fd, TUNGETFEATURES, &features) == 0 &&
+        features & IFF_ONE_QUEUE) {
+        ifr.ifr_flags |= IFF_ONE_QUEUE;
+    }
 
+    if (*vnet_hdr) {
         if (ioctl(fd, TUNGETFEATURES, &features) == 0 &&
             features & IFF_VNET_HDR) {
             *vnet_hdr = 1;
@@ -78,8 +82,6 @@
     }
 
     if (mq_required) {
-        unsigned int features;
-
         if ((ioctl(fd, TUNGETFEATURES, &features) != 0) ||
             !(features & IFF_MULTI_QUEUE)) {
             error_report("multiqueue required, but no kernel "
diff --git a/net/tap-linux.h b/net/tap-linux.h
index 65087e1..1cf35d4 100644
--- a/net/tap-linux.h
+++ b/net/tap-linux.h
@@ -34,10 +34,11 @@
 #endif
 
 /* TUNSETIFF ifr flags */
-#define IFF_TAP		0x0002
-#define IFF_NO_PI	0x1000
-#define IFF_VNET_HDR	0x4000
-#define IFF_MULTI_QUEUE 0x0100
+#define IFF_TAP          0x0002
+#define IFF_NO_PI        0x1000
+#define IFF_ONE_QUEUE    0x2000
+#define IFF_VNET_HDR     0x4000
+#define IFF_MULTI_QUEUE  0x0100
 #define IFF_ATTACH_QUEUE 0x0200
 #define IFF_DETACH_QUEUE 0x0400
 
diff --git a/net/tap.c b/net/tap.c
index 48c254e..daab350 100644
--- a/net/tap.c
+++ b/net/tap.c
@@ -693,6 +693,13 @@
     queues = tap->has_queues ? tap->queues : 1;
     vhostfdname = tap->has_vhostfd ? tap->vhostfd : NULL;
 
+    /* QEMU vlans does not support multiqueue tap, in this case peer is set.
+     * For -netdev, peer is always NULL. */
+    if (peer && (tap->has_queues || tap->has_fds || tap->has_vhostfds)) {
+        error_report("Multiqueue tap cannnot be used with QEMU vlans");
+        return -1;
+    }
+
     if (tap->has_fd) {
         if (tap->has_ifname || tap->has_script || tap->has_downscript ||
             tap->has_vnet_hdr || tap->has_helper || tap->has_queues ||
diff --git a/qemu-options.hx b/qemu-options.hx
index 797d992..863069f 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1408,7 +1408,8 @@
 #ifdef CONFIG_VDE
     "vde|"
 #endif
-    "socket],id=str[,option][,option][,...]\n", QEMU_ARCH_ALL)
+    "socket|"
+    "hubport],id=str[,option][,option][,...]\n", QEMU_ARCH_ALL)
 STEXI
 @item -net nic[,vlan=@var{n}][,macaddr=@var{mac}][,model=@var{type}] [,name=@var{name}][,addr=@var{addr}][,vectors=@var{v}]
 @findex -net
@@ -1730,6 +1731,14 @@
 qemu-system-i386 linux.img -net nic -net vde,sock=/tmp/myswitch
 @end example
 
+@item -netdev hubport,id=@var{id},hubid=@var{hubid}
+
+Create a hub port on QEMU "vlan" @var{hubid}.
+
+The hubport netdev lets you connect a NIC to a QEMU "vlan" instead of a single
+netdev.  @code{-net} and @code{-device} with parameter @option{vlan} create the
+required hub automatically.
+
 @item -net dump[,vlan=@var{n}][,file=@var{file}][,len=@var{len}]
 Dump network traffic on VLAN @var{n} to file @var{file} (@file{qemu-vlan0.pcap} by default).
 At most @var{len} bytes (64k by default) per packet are stored. The file format is
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 799adea..95022e2 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -822,7 +822,7 @@
 -> { "execute": "netdev_add", "arguments": { "type": "user", "id": "netdev1" } }
 <- { "return": {} }
 
-Note: The supported device options are the same ones supported by the '-net'
+Note: The supported device options are the same ones supported by the '-netdev'
       command-line argument, which are listed in the '-help' output or QEMU's
       manual