From 89d125ee4e8a4a041b83c7157a620f07fda52a65 Mon Sep 17 00:00:00 2001
From: Dor Laor <dor@cloudius-systems.com>
Date: Mon, 25 Feb 2013 10:14:05 +0200
Subject: [PATCH] Make an initial receive only working virtio-net

---
 core/debug.cc           |   1 +
 drivers/virtio-net.cc   | 175 ++++++++++++++++++++++++++++-
 drivers/virtio-net.hh   | 240 +++++++++++++++++++++++++++++++++++-----
 drivers/virtio-vring.cc |   7 ++
 drivers/virtio-vring.hh |   1 +
 loader.cc               |   2 +-
 6 files changed, 395 insertions(+), 31 deletions(-)

diff --git a/core/debug.cc b/core/debug.cc
index 21d7963a0..f16b85ecd 100644
--- a/core/debug.cc
+++ b/core/debug.cc
@@ -25,6 +25,7 @@ bool logger::parse_configuration(void)
     // FIXME: read configuration from a file
     add_tag("virtio", logger_info);
     add_tag("virtio-blk", logger_info);
+    add_tag("virtio-net", logger_debug);
     add_tag("pci", logger_info);
 
     // Tests
diff --git a/drivers/virtio-net.cc b/drivers/virtio-net.cc
index 6bd1b3c5c..a11918867 100644
--- a/drivers/virtio-net.cc
+++ b/drivers/virtio-net.cc
@@ -1,27 +1,80 @@
+
+#include <sys/cdefs.h>
+
 #include "drivers/virtio.hh"
 #include "drivers/virtio-net.hh"
 #include "drivers/pci-device.hh"
+#include "interrupt.hh"
+
+#include "mempool.hh"
+#include "mmu.hh"
+#include "sglist.hh"
 
+#include <sstream>
+#include <string>
+#include <string.h>
+#include <map>
+#include <errno.h>
 #include "debug.hh"
 
+#include "sched.hh"
+#include "drivers/clock.hh"
+#include "drivers/clockevent.hh"
+
+#include <osv/device.h>
+
+using namespace memory;
+using sched::thread;
+
+// TODO list
+// irq thread affinity and tx affinity
+// Mergable buffers
+// tx zero copy
+// vlans?
 
 namespace virtio {
 
+    int virtio_net::_instance = 0;
 
-    virtio_net::virtio_net()
-        : virtio_driver(VIRTIO_NET_DEVICE_ID)
+    #define virtio_net_tag "virtio-net"
+    #define virtio_net_d(fmt)   logger::instance()->log(virtio_net_tag, logger::logger_debug, (fmt))
+    #define virtio_net_i(fmt)   logger::instance()->log(virtio_net_tag, logger::logger_info, (fmt))
+    #define virtio_net_w(fmt)   logger::instance()->log(virtio_net_tag, logger::logger_warn, (fmt))
+    #define virtio_net_e(fmt)   logger::instance()->log(virtio_net_tag, logger::logger_error, (fmt))
+
+
+    virtio_net::virtio_net(unsigned dev_idx)
+        : virtio_driver(VIRTIO_NET_DEVICE_ID, dev_idx)
     {
+        std::stringstream ss;
+        ss << "virtio-net" << dev_idx;
 
+        _driver_name = ss.str();
+        virtio_i(fmt("VIRTIO NET INSTANCE %d") % dev_idx);
+        _id = _instance++;
     }
 
     virtio_net::~virtio_net()
     {
+        //TODO: In theory maintain the list of free instances and gc it
+        // including the thread objects and their stack
     }
 
     bool virtio_net::load(void)
     {
         virtio_driver::load();
         
+        read_config();
+
+        //register the 3 irq callback for the net
+        msix_isr_list* isrs = new msix_isr_list;
+        thread* isr = new thread([this] { this->receiver(); });
+        isr->start();
+        isrs->insert(std::make_pair(0, isr));
+        interrupt_manager::instance()->easy_register(_dev, *isrs);
+
+        fill_rx_ring();
+
         _dev->add_dev_status(VIRTIO_CONFIG_S_DRIVER_OK);
 
         return true;
@@ -32,5 +85,121 @@ namespace virtio {
         return (true);
     }
 
-}
+    bool virtio_net::read_config()
+    {
+        //read all of the net config  in one shot
+        _dev->virtio_conf_read(_dev->virtio_pci_config_offset(), &_config, sizeof(_config));
+
+        if (_dev->get_guest_feature_bit(VIRTIO_NET_F_MAC))
+            virtio_i(fmt("The mac addr of the device is %x:%x:%x:%x:%x:%x") %
+                    (u32)_config.mac[0] %
+                    (u32)_config.mac[1] %
+                    (u32)_config.mac[2] %
+                    (u32)_config.mac[3] %
+                    (u32)_config.mac[4] %
+                    (u32)_config.mac[5]);
+
+        return true;
+    }
+    struct ethhdr {
+            unsigned char   h_dest[6];       /* destination eth addr */
+            unsigned char   h_source[6];     /* source ether addr    */
+            u16          h_proto;                /* packet type ID field */
+    } __attribute__((packed));
+
+
+    struct icmphdr {
+      u8          type;
+      u8          code;
+      u16       checksum;
+      union {
+            struct {
+                    u16  id;
+                    u16  sequence;
+            } echo;
+            u32  gateway;
+            struct {
+                    u16  __unused;
+                    u16  mtu;
+            } frag;
+      } un;
+    };
+
+    void virtio_net::receiver() {
+        vring* queue = _dev->get_virt_queue(0);
+
+        while (1) {
+            thread::wait_until([this] {
+                vring* queue = this->_dev->get_virt_queue(0);
+                virtio_net_d(fmt("\t ----> IRQ: woke in wait)until, cond=%d") % (int)queue->used_ring_not_empty());
+                return queue->used_ring_not_empty();
+            });
+
+            virtio_net_d(fmt("\t ----> IRQ: virtio_d - net thread awaken"));
 
+            int i = 0;
+            virtio_net_req * req;
+
+            while((req = static_cast<virtio_net_req*>(queue->get_buf())) != nullptr) {
+                virtio_net_d(fmt("\t got hdr len:%d = %d ") % i++ % (int)req->hdr.hdr_len);
+
+                auto ii = req->payload._nodes.begin();
+                ii++;
+                char*buf = reinterpret_cast<char*>(mmu::phys_to_virt(ii->_paddr));
+                virtio_net_d(fmt("\t len=%d") % ii->_len);
+
+                ethhdr* eh = reinterpret_cast<ethhdr*>(buf);
+                virtio_net_d(fmt("The src %x:%x:%x:%x:%x:%x dst %x:%x:%x:%x:%x:%x type %d ") %
+                        (u32)eh->h_source[0] %
+                        (u32)eh->h_source[1] %
+                        (u32)eh->h_source[2] %
+                        (u32)eh->h_source[3] %
+                        (u32)eh->h_source[4] %
+                        (u32)eh->h_source[5] %
+                        (u32)eh->h_dest[0] %
+                        (u32)eh->h_dest[1] %
+                        (u32)eh->h_dest[2] %
+                        (u32)eh->h_dest[3] %
+                        (u32)eh->h_dest[4] %
+                        (u32)eh->h_dest[5] %
+                        (u32)eh->h_proto);
+
+            }
+
+        }
+
+    }
+
+    static const int page_size = 4096;
+
+    void virtio_net::fill_rx_ring()
+    {
+        vring* queue = _dev->get_virt_queue(0);
+        virtio_net_d(fmt("%s") % __FUNCTION__);
+
+        while (queue->avail_ring_has_room(2)) {
+            virtio_net_req *req = new virtio_net_req;
+
+            void* buf = malloc(page_size);
+            memset(buf, 0, page_size);
+            req->payload.add(mmu::virt_to_phys(buf), page_size);
+            req->payload.add(mmu::virt_to_phys(static_cast<void*>(&req->hdr)), sizeof(struct virtio_net_hdr), true);
+
+            virtio_net_d(fmt("%s adding") % __FUNCTION__);
+
+            if (!queue->add_buf(&req->payload,0,2,req)) {
+                delete req;
+                break;
+            }
+        }
+
+        queue->kick();
+    }
+
+    u32 virtio_net::get_driver_features(void)
+    {
+        u32 base = virtio_driver::get_driver_features();
+        return (base | ( 1 << VIRTIO_NET_F_MAC));
+    }
+
+}
diff --git a/drivers/virtio-net.hh b/drivers/virtio-net.hh
index 89d4f2ab3..01da72f68 100644
--- a/drivers/virtio-net.hh
+++ b/drivers/virtio-net.hh
@@ -1,8 +1,35 @@
 #ifndef VIRTIO_NET_DRIVER_H
 #define VIRTIO_NET_DRIVER_H
 
+/* This header is BSD licensed so anyone can use the definitions to implement
+ * compatible drivers/servers.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of IBM nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL IBM OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE. */
+
 #include "drivers/virtio.hh"
 #include "drivers/pci-device.hh"
+#include "sglist.hh"
 
 namespace virtio {
 
@@ -11,48 +38,207 @@ namespace virtio {
 
         // The feature bitmap for virtio net
         enum NetFeatures {
-            VIRTIO_NET_F_CSUM=0,            /* Host handles pkts w/ partial csum */
-            VIRTIO_NET_F_GUEST_CSUM=1,      /* Guest handles pkts w/ partial csum */
-            VIRTIO_NET_F_MAC=5,             /* Host has given MAC address. */
-            VIRTIO_NET_F_GSO=6,             /* Host handles pkts w/ any GSO type */
-            VIRTIO_NET_F_GUEST_TSO4=7,      /* Guest can handle TSOv4 in. */
-            VIRTIO_NET_F_GUEST_TSO6=8,      /* Guest can handle TSOv6 in. */
-            VIRTIO_NET_F_GUEST_ECN=9,       /* Guest can handle TSO[6] w/ ECN in. */
-            VIRTIO_NET_F_GUEST_UFO=10,      /* Guest can handle UFO in. */
-            VIRTIO_NET_F_HOST_TSO4=11,      /* Host can handle TSOv4 in. */
-            VIRTIO_NET_F_HOST_TSO6=12,      /* Host can handle TSOv6 in. */
-            VIRTIO_NET_F_HOST_ECN=13,       /* Host can handle TSO[6] w/ ECN in. */
-            VIRTIO_NET_F_HOST_UFO=14,       /* Host can handle UFO in. */
-            VIRTIO_NET_F_MRG_RXBUF=15,      /* Host can merge receive buffers. */
-            VIRTIO_NET_F_STATUS=16,         /* virtio_net_config.status available */
-            VIRTIO_NET_F_CTRL_VQ=17,        /* Control channel available */
-            VIRTIO_NET_F_CTRL_RX=18,        /* Control channel RX mode support */
-            VIRTIO_NET_F_CTRL_VLAN=19,      /* Control channel VLAN filtering */
-            VIRTIO_NET_F_CTRL_RX_EXTRA=20,  /* Extra RX mode control support */
-            VIRTIO_NET_F_GUEST_ANNOUNCE=21  /* Guest can announce device on the 
-                                                                               network */
+            VIRTIO_NET_F_CSUM = 0,       /* Host handles pkts w/ partial csum */
+            VIRTIO_NET_F_GUEST_CSUM = 1,       /* Guest handles pkts w/ partial csum */
+            VIRTIO_NET_F_MAC = 5,       /* Host has given MAC address. */
+            VIRTIO_NET_F_GSO = 6,       /* Host handles pkts w/ any GSO type */
+            VIRTIO_NET_F_GUEST_TSO4 = 7,       /* Guest can handle TSOv4 in. */
+            VIRTIO_NET_F_GUEST_TSO6 = 8,       /* Guest can handle TSOv6 in. */
+            VIRTIO_NET_F_GUEST_ECN = 9,       /* Guest can handle TSO[6] w/ ECN in. */
+            VIRTIO_NET_F_GUEST_UFO = 10,      /* Guest can handle UFO in. */
+            VIRTIO_NET_F_HOST_TSO4 = 11,      /* Host can handle TSOv4 in. */
+            VIRTIO_NET_F_HOST_TSO6 = 12,      /* Host can handle TSOv6 in. */
+            VIRTIO_NET_F_HOST_ECN = 13,      /* Host can handle TSO[6] w/ ECN in. */
+            VIRTIO_NET_F_HOST_UFO = 14,      /* Host can handle UFO in. */
+            VIRTIO_NET_F_MRG_RXBUF = 15,      /* Host can merge receive buffers. */
+            VIRTIO_NET_F_STATUS = 16,      /* virtio_net_config.status available */
+            VIRTIO_NET_F_CTRL_VQ  = 17,      /* Control channel available */
+            VIRTIO_NET_F_CTRL_RX = 18,      /* Control channel RX mode support */
+            VIRTIO_NET_F_CTRL_VLAN = 19,      /* Control channel VLAN filtering */
+            VIRTIO_NET_F_CTRL_RX_EXTRA = 20,   /* Extra RX mode control support */
+            VIRTIO_NET_F_GUEST_ANNOUNCE = 21,  /* Guest can announce device on the network */
+            VIRTIO_NET_F_MQ = 22,      /* Device supports Receive Flow Steering */
+            VIRTIO_NET_F_CTRL_MAC_ADDR = 23,   /* Set MAC address */
         };
 
         enum {
             VIRTIO_NET_DEVICE_ID=0x1000,
+            VIRTIO_NET_S_LINK_UP = 1,       /* Link is up */
+            VIRTIO_NET_S_ANNOUNCE = 2,       /* Announcement is needed */
+            VIRTIO_NET_OK = 0,
+            VIRTIO_NET_ERR = 1,
+            /*
+             * Control the RX mode, ie. promisucous, allmulti, etc...
+             * All commands require an "out" sg entry containing a 1 byte
+             * state value, zero = disable, non-zero = enable.  Commands
+             * 0 and 1 are supported with the VIRTIO_NET_F_CTRL_RX feature.
+             * Commands 2-5 are added with VIRTIO_NET_F_CTRL_RX_EXTRA.
+             */
+            VIRTIO_NET_CTRL_RX = 0,
+            VIRTIO_NET_CTRL_RX_PROMISC = 0,
+            VIRTIO_NET_CTRL_RX_ALLMULTI = 1,
+            VIRTIO_NET_CTRL_RX_ALLUNI = 2,
+            VIRTIO_NET_CTRL_RX_NOMULTI = 3,
+            VIRTIO_NET_CTRL_RX_NOUNI = 4,
+            VIRTIO_NET_CTRL_RX_NOBCAST = 5,
+
+            VIRTIO_NET_CTRL_MAC = 1,
+            VIRTIO_NET_CTRL_MAC_TABLE_SET = 0,
+            VIRTIO_NET_CTRL_MAC_ADDR_SET = 1,
+
+            /*
+             * Control VLAN filtering
+             *
+             * The VLAN filter table is controlled via a simple ADD/DEL interface.
+             * VLAN IDs not added may be filterd by the hypervisor.  Del is the
+             * opposite of add.  Both commands expect an out entry containing a 2
+             * byte VLAN ID.  VLAN filterting is available with the
+             * VIRTIO_NET_F_CTRL_VLAN feature bit.
+             */
+            VIRTIO_NET_CTRL_VLAN = 2,
+            VIRTIO_NET_CTRL_VLAN_ADD = 0,
+            VIRTIO_NET_CTRL_VLAN_DEL = 1,
+
+            /*
+             * Control link announce acknowledgement
+             *
+             * The command VIRTIO_NET_CTRL_ANNOUNCE_ACK is used to indicate that
+             * driver has recevied the notification; device would clear the
+             * VIRTIO_NET_S_ANNOUNCE bit in the status field after it receives
+             * this command.
+             */
+            VIRTIO_NET_CTRL_ANNOUNCE = 3,
+            VIRTIO_NET_CTRL_ANNOUNCE_ACK = 0,
+
+            VIRTIO_NET_CTRL_MQ = 4,
+            VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET = 0,
+            VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN = 1,
+            VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX = 0x8000,
+
+            ETH_ALEN = 14,
         };
 
-        
-        virtio_net();
-        virtual ~virtio_net();
+        struct virtio_net_config {
+            /* The config defining mac address (if VIRTIO_NET_F_MAC) */
+            u8 mac[6];
+            /* See VIRTIO_NET_F_STATUS and VIRTIO_NET_S_* above */
+            u16 status;
+            /* Maximum number of each of transmit and receive queues;
+             * see VIRTIO_NET_F_MQ and VIRTIO_NET_CTRL_MQ.
+             * Legal values are between 1 and 0x8000
+             */
+            u16 max_virtqueue_pairs;
+        } __attribute__((packed));
+
+        /* This is the first element of the scatter-gather list.  If you don't
+         * specify GSO or CSUM features, you can simply ignore the header. */
+        struct virtio_net_hdr {
+            enum {
+                VIRTIO_NET_HDR_F_NEEDS_CSUM  = 1,       // Use csum_start, csum_offset
+                VIRTIO_NET_HDR_F_DATA_VALID = 2,       // Csum is valid
+            };
+            u8 flags;
+            enum {
+                VIRTIO_NET_HDR_GSO_NONE = 0,       // Not a GSO frame
+                VIRTIO_NET_HDR_GSO_TCPV4 = 1,       // GSO frame, IPv4 TCP (TSO)
+                VIRTIO_NET_HDR_GSO_UDP = 3,       // GSO frame, IPv4 UDP (UFO)
+                VIRTIO_NET_HDR_GSO_TCPV6 = 4,       // GSO frame, IPv6 TCP
+                VIRTIO_NET_HDR_GSO_ECN = 0x80,    // TCP has ECN set
+            };
+            u8 gso_type;
+            u16 hdr_len;          /* Ethernet + IP + tcp/udp hdrs */
+            u16 gso_size;         /* Bytes to append to hdr_len per frame */
+            u16 csum_start;       /* Position to start checksumming from */
+            u16 csum_offset;      /* Offset after that to place checksum */
+        };
+
+        /* This is the version of the header to use when the MRG_RXBUF
+         * feature has been negotiated. */
+        struct virtio_net_hdr_mrg_rxbuf {
+            struct virtio_net_hdr hdr;
+            u16 num_buffers;      /* Number of merged rx buffers */
+        };
 
-        virtual const std::string get_name(void) { return "virtio-net"; }
+        /*
+         * Control virtqueue data structures
+         *
+         * The control virtqueue expects a header in the first sg entry
+         * and an ack/status response in the last entry.  Data for the
+         * command goes in between.
+         */
+        struct virtio_net_ctrl_hdr {
+                u8 class_t;
+                u8 cmd;
+        } __attribute__((packed));
 
+        typedef u8 virtio_net_ctrl_ack;
+
+        /*
+         * Control the MAC
+         *
+         * The MAC filter table is managed by the hypervisor, the guest should
+         * assume the size is infinite.  Filtering should be considered
+         * non-perfect, ie. based on hypervisor resources, the guest may
+         * received packets from sources not specified in the filter list.
+         *
+         * In addition to the class/cmd header, the TABLE_SET command requires
+         * two out scatterlists.  Each contains a 4 byte count of entries followed
+         * by a concatenated byte stream of the ETH_ALEN MAC addresses.  The
+         * first sg list contains unicast addresses, the second is for multicast.
+         * This functionality is present if the VIRTIO_NET_F_CTRL_RX feature
+         * is available.
+         *
+         * The ADDR_SET command requests one out scatterlist, it contains a
+         * 6 bytes MAC address. This functionality is present if the
+         * VIRTIO_NET_F_CTRL_MAC_ADDR feature is available.
+         */
+        struct virtio_net_ctrl_mac {
+                u32 entries;
+                u8 macs[][ETH_ALEN];
+        } __attribute__((packed));
+
+        /*
+         * Control Receive Flow Steering
+         *
+         * The command VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET
+         * enables Receive Flow Steering, specifying the number of the transmit and
+         * receive queues that will be used. After the command is consumed and acked by
+         * the device, the device will not steer new packets on receive virtqueues
+         * other than specified nor read from transmit virtqueues other than specified.
+         * Accordingly, driver should not transmit new packets  on virtqueues other than
+         * specified.
+         */
+        struct virtio_net_ctrl_mq {
+                u16 virtqueue_pairs;
+        };
+
+        struct virtio_net_req {
+            virtio_net_hdr hdr;
+            sglist payload;
+        };
+
+        virtio_net(unsigned dev_idx=0);
+        virtual ~virtio_net();
+
+        virtual const std::string get_name(void) { return _driver_name; }
         virtual bool load(void);
         virtual bool unload(void);
+        bool read_config();
+
+        virtual u32 get_driver_features(void);
 
-        virtual u32 get_driver_features(void) { return ((1 << VIRTIO_NET_F_CSUM) | (1 << VIRTIO_NET_F_MAC)); }
+        void receiver();
+        void fill_rx_ring();
 
     private:
 
-        
-    };
+        std::string _driver_name;
+        virtio_net_config _config;
 
+        //maintains the virtio instance number for multiple drives
+        static int _instance;
+        int _id;
+    };
 }
 
 #endif
diff --git a/drivers/virtio-vring.cc b/drivers/virtio-vring.cc
index f4f59f866..6d6ba78a5 100644
--- a/drivers/virtio-vring.cc
+++ b/drivers/virtio-vring.cc
@@ -184,6 +184,13 @@ namespace virtio {
         return (_avail_count > 0);
     }
 
+    bool vring::avail_ring_has_room(int descriptors)
+    {
+        if (_dev->get_indirect_buf_cap())
+            descriptors = 1;
+        return (_avail_count >= descriptors);
+    }
+
     bool vring::used_ring_not_empty()
     {
         return (_used_guest_head != _used->_idx);
diff --git a/drivers/virtio-vring.hh b/drivers/virtio-vring.hh
index ca8cb6fa8..bb274229b 100644
--- a/drivers/virtio-vring.hh
+++ b/drivers/virtio-vring.hh
@@ -122,6 +122,7 @@ class virtio_device;
         void* get_buf();
         bool used_ring_not_empty();
         bool avail_ring_not_empty();
+        bool avail_ring_has_room(int n);
         bool kick();
 
         // The following is used with USED_EVENT_IDX and AVAIL_EVENT_IDX
diff --git a/loader.cc b/loader.cc
index cb9598fc6..cd8d029eb 100644
--- a/loader.cc
+++ b/loader.cc
@@ -136,7 +136,7 @@ void* do_main_thread(void *_args)
     hw::driver_manager* drvman = hw::driver_manager::instance();
     drvman->register_driver(new virtio::virtio_blk(0));
     drvman->register_driver(new virtio::virtio_blk(1));
-    drvman->register_driver(new virtio::virtio_net());
+    drvman->register_driver(new virtio::virtio_net(0));
     drvman->load_all();
     drvman->list_drivers();
 
-- 
GitLab