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