diff --git a/default-configs/pci.mak b/default-configs/pci.mak index ee2d18d5f2..ce56d58055 100644 --- a/default-configs/pci.mak +++ b/default-configs/pci.mak @@ -13,6 +13,7 @@ CONFIG_LSI_SCSI_PCI=y CONFIG_MEGASAS_SCSI_PCI=y CONFIG_RTL8139_PCI=y CONFIG_E1000_PCI=y +CONFIG_VMXNET3_PCI=y CONFIG_IDE_CORE=y CONFIG_IDE_QDEV=y CONFIG_IDE_PCI=y diff --git a/hw/Makefile.objs b/hw/Makefile.objs index 11812c6a4e..d0b2ecb97f 100644 --- a/hw/Makefile.objs +++ b/hw/Makefile.objs @@ -119,6 +119,8 @@ common-obj-$(CONFIG_PCNET_PCI) += pcnet-pci.o common-obj-$(CONFIG_PCNET_COMMON) += pcnet.o common-obj-$(CONFIG_E1000_PCI) += e1000.o common-obj-$(CONFIG_RTL8139_PCI) += rtl8139.o +common-obj-$(CONFIG_VMXNET3_PCI) += vmxnet_tx_pkt.o vmxnet_rx_pkt.o +common-obj-$(CONFIG_VMXNET3_PCI) += vmxnet3.o common-obj-$(CONFIG_SMC91C111) += smc91c111.o common-obj-$(CONFIG_LAN9118) += lan9118.o diff --git a/hw/pci/pci.h b/hw/pci/pci.h index f340fe57c9..3beb70bf9a 100644 --- a/hw/pci/pci.h +++ b/hw/pci/pci.h @@ -60,6 +60,7 @@ #define PCI_DEVICE_ID_VMWARE_NET 0x0720 #define PCI_DEVICE_ID_VMWARE_SCSI 0x0730 #define PCI_DEVICE_ID_VMWARE_IDE 0x1729 +#define PCI_DEVICE_ID_VMWARE_VMXNET3 0x07B0 /* Intel (0x8086) */ #define PCI_DEVICE_ID_INTEL_82551IT 0x1209 diff --git a/hw/vmware_utils.h b/hw/vmware_utils.h new file mode 100644 index 0000000000..5307e2ccc9 --- /dev/null +++ b/hw/vmware_utils.h @@ -0,0 +1,143 @@ +/* + * QEMU VMWARE paravirtual devices - auxiliary code + * + * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com) + * + * Developed by Daynix Computing LTD (http://www.daynix.com) + * + * Authors: + * Dmitry Fleytman + * Yan Vugenfirer + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef VMWARE_UTILS_H +#define VMWARE_UTILS_H + +#include "qemu/range.h" + +#ifndef VMW_SHPRN +#define VMW_SHPRN(fmt, ...) do {} while (0) +#endif + +/* + * Shared memory access functions with byte swap support + * Each function contains printout for reverse-engineering needs + * + */ +static inline void +vmw_shmem_read(hwaddr addr, void *buf, int len) +{ + VMW_SHPRN("SHMEM r: %" PRIx64 ", len: %d to %p", addr, len, buf); + cpu_physical_memory_read(addr, buf, len); +} + +static inline void +vmw_shmem_write(hwaddr addr, void *buf, int len) +{ + VMW_SHPRN("SHMEM w: %" PRIx64 ", len: %d to %p", addr, len, buf); + cpu_physical_memory_write(addr, buf, len); +} + +static inline void +vmw_shmem_rw(hwaddr addr, void *buf, int len, int is_write) +{ + VMW_SHPRN("SHMEM r/w: %" PRIx64 ", len: %d (to %p), is write: %d", + addr, len, buf, is_write); + + cpu_physical_memory_rw(addr, buf, len, is_write); +} + +static inline void +vmw_shmem_set(hwaddr addr, uint8 val, int len) +{ + int i; + VMW_SHPRN("SHMEM set: %" PRIx64 ", len: %d (value 0x%X)", addr, len, val); + + for (i = 0; i < len; i++) { + cpu_physical_memory_write(addr + i, &val, 1); + } +} + +static inline uint32_t +vmw_shmem_ld8(hwaddr addr) +{ + uint8_t res = ldub_phys(addr); + VMW_SHPRN("SHMEM load8: %" PRIx64 " (value 0x%X)", addr, res); + return res; +} + +static inline void +vmw_shmem_st8(hwaddr addr, uint8_t value) +{ + VMW_SHPRN("SHMEM store8: %" PRIx64 " (value 0x%X)", addr, value); + stb_phys(addr, value); +} + +static inline uint32_t +vmw_shmem_ld16(hwaddr addr) +{ + uint16_t res = lduw_le_phys(addr); + VMW_SHPRN("SHMEM load16: %" PRIx64 " (value 0x%X)", addr, res); + return res; +} + +static inline void +vmw_shmem_st16(hwaddr addr, uint16_t value) +{ + VMW_SHPRN("SHMEM store16: %" PRIx64 " (value 0x%X)", addr, value); + stw_le_phys(addr, value); +} + +static inline uint32_t +vmw_shmem_ld32(hwaddr addr) +{ + uint32_t res = ldl_le_phys(addr); + VMW_SHPRN("SHMEM load32: %" PRIx64 " (value 0x%X)", addr, res); + return res; +} + +static inline void +vmw_shmem_st32(hwaddr addr, uint32_t value) +{ + VMW_SHPRN("SHMEM store32: %" PRIx64 " (value 0x%X)", addr, value); + stl_le_phys(addr, value); +} + +static inline uint64_t +vmw_shmem_ld64(hwaddr addr) +{ + uint64_t res = ldq_le_phys(addr); + VMW_SHPRN("SHMEM load64: %" PRIx64 " (value %" PRIx64 ")", addr, res); + return res; +} + +static inline void +vmw_shmem_st64(hwaddr addr, uint64_t value) +{ + VMW_SHPRN("SHMEM store64: %" PRIx64 " (value %" PRIx64 ")", addr, value); + stq_le_phys(addr, value); +} + +/* Macros for simplification of operations on array-style registers */ + +/* + * Whether lies inside of array-style register defined by , + * number of elements () and element size () + * +*/ +#define VMW_IS_MULTIREG_ADDR(addr, base, cnt, regsize) \ + range_covers_byte(base, cnt * regsize, addr) + +/* + * Returns index of given register () in array-style register defined by + * and element size () + * +*/ +#define VMW_MULTIREG_IDX_BY_ADDR(addr, base, regsize) \ + (((addr) - (base)) / (regsize)) + +#endif diff --git a/hw/vmxnet3.c b/hw/vmxnet3.c new file mode 100644 index 0000000000..925be8084a --- /dev/null +++ b/hw/vmxnet3.c @@ -0,0 +1,2461 @@ +/* + * QEMU VMWARE VMXNET3 paravirtual NIC + * + * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com) + * + * Developed by Daynix Computing LTD (http://www.daynix.com) + * + * Authors: + * Dmitry Fleytman + * Tamir Shomer + * Yan Vugenfirer + * + * This work is licensed under the terms of the GNU GPL, version 2. + * See the COPYING file in the top-level directory. + * + */ + +#include "hw.h" +#include "pci/pci.h" +#include "net/net.h" +#include "virtio-net.h" +#include "net/tap.h" +#include "net/checksum.h" +#include "sysemu/sysemu.h" +#include "qemu-common.h" +#include "qemu/bswap.h" +#include "pci/msix.h" +#include "pci/msi.h" + +#include "vmxnet3.h" +#include "vmxnet_debug.h" +#include "vmware_utils.h" +#include "vmxnet_tx_pkt.h" +#include "vmxnet_rx_pkt.h" + +#define PCI_DEVICE_ID_VMWARE_VMXNET3_REVISION 0x1 +#define VMXNET3_MSIX_BAR_SIZE 0x2000 + +#define VMXNET3_BAR0_IDX (0) +#define VMXNET3_BAR1_IDX (1) +#define VMXNET3_MSIX_BAR_IDX (2) + +#define VMXNET3_OFF_MSIX_TABLE (0x000) +#define VMXNET3_OFF_MSIX_PBA (0x800) + +/* Link speed in Mbps should be shifted by 16 */ +#define VMXNET3_LINK_SPEED (1000 << 16) + +/* Link status: 1 - up, 0 - down. */ +#define VMXNET3_LINK_STATUS_UP 0x1 + +/* Least significant bit should be set for revision and version */ +#define VMXNET3_DEVICE_VERSION 0x1 +#define VMXNET3_DEVICE_REVISION 0x1 + +/* Macros for rings descriptors access */ +#define VMXNET3_READ_TX_QUEUE_DESCR8(dpa, field) \ + (vmw_shmem_ld8(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field))) + +#define VMXNET3_WRITE_TX_QUEUE_DESCR8(dpa, field, value) \ + (vmw_shmem_st8(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field, value))) + +#define VMXNET3_READ_TX_QUEUE_DESCR32(dpa, field) \ + (vmw_shmem_ld32(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field))) + +#define VMXNET3_WRITE_TX_QUEUE_DESCR32(dpa, field, value) \ + (vmw_shmem_st32(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field), value)) + +#define VMXNET3_READ_TX_QUEUE_DESCR64(dpa, field) \ + (vmw_shmem_ld64(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field))) + +#define VMXNET3_WRITE_TX_QUEUE_DESCR64(dpa, field, value) \ + (vmw_shmem_st64(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field), value)) + +#define VMXNET3_READ_RX_QUEUE_DESCR64(dpa, field) \ + (vmw_shmem_ld64(dpa + offsetof(struct Vmxnet3_RxQueueDesc, field))) + +#define VMXNET3_READ_RX_QUEUE_DESCR32(dpa, field) \ + (vmw_shmem_ld32(dpa + offsetof(struct Vmxnet3_RxQueueDesc, field))) + +#define VMXNET3_WRITE_RX_QUEUE_DESCR64(dpa, field, value) \ + (vmw_shmem_st64(dpa + offsetof(struct Vmxnet3_RxQueueDesc, field), value)) + +#define VMXNET3_WRITE_RX_QUEUE_DESCR8(dpa, field, value) \ + (vmw_shmem_st8(dpa + offsetof(struct Vmxnet3_RxQueueDesc, field), value)) + +/* Macros for guest driver shared area access */ +#define VMXNET3_READ_DRV_SHARED64(shpa, field) \ + (vmw_shmem_ld64(shpa + offsetof(struct Vmxnet3_DriverShared, field))) + +#define VMXNET3_READ_DRV_SHARED32(shpa, field) \ + (vmw_shmem_ld32(shpa + offsetof(struct Vmxnet3_DriverShared, field))) + +#define VMXNET3_WRITE_DRV_SHARED32(shpa, field, val) \ + (vmw_shmem_st32(shpa + offsetof(struct Vmxnet3_DriverShared, field), val)) + +#define VMXNET3_READ_DRV_SHARED16(shpa, field) \ + (vmw_shmem_ld16(shpa + offsetof(struct Vmxnet3_DriverShared, field))) + +#define VMXNET3_READ_DRV_SHARED8(shpa, field) \ + (vmw_shmem_ld8(shpa + offsetof(struct Vmxnet3_DriverShared, field))) + +#define VMXNET3_READ_DRV_SHARED(shpa, field, b, l) \ + (vmw_shmem_read(shpa + offsetof(struct Vmxnet3_DriverShared, field), b, l)) + +#define VMXNET_FLAG_IS_SET(field, flag) (((field) & (flag)) == (flag)) + +#define TYPE_VMXNET3 "vmxnet3" +#define VMXNET3(obj) OBJECT_CHECK(VMXNET3State, (obj), TYPE_VMXNET3) + +/* Cyclic ring abstraction */ +typedef struct { + hwaddr pa; + size_t size; + size_t cell_size; + size_t next; + uint8_t gen; +} Vmxnet3Ring; + +static inline void vmxnet3_ring_init(Vmxnet3Ring *ring, + hwaddr pa, + size_t size, + size_t cell_size, + bool zero_region) +{ + ring->pa = pa; + ring->size = size; + ring->cell_size = cell_size; + ring->gen = VMXNET3_INIT_GEN; + ring->next = 0; + + if (zero_region) { + vmw_shmem_set(pa, 0, size * cell_size); + } +} + +#define VMXNET3_RING_DUMP(macro, ring_name, ridx, r) \ + macro("%s#%d: base %" PRIx64 " size %lu cell_size %lu gen %d next %lu", \ + (ring_name), (ridx), \ + (r)->pa, (r)->size, (r)->cell_size, (r)->gen, (r)->next) + +static inline void vmxnet3_ring_inc(Vmxnet3Ring *ring) +{ + if (++ring->next >= ring->size) { + ring->next = 0; + ring->gen ^= 1; + } +} + +static inline void vmxnet3_ring_dec(Vmxnet3Ring *ring) +{ + if (ring->next-- == 0) { + ring->next = ring->size - 1; + ring->gen ^= 1; + } +} + +static inline hwaddr vmxnet3_ring_curr_cell_pa(Vmxnet3Ring *ring) +{ + return ring->pa + ring->next * ring->cell_size; +} + +static inline void vmxnet3_ring_read_curr_cell(Vmxnet3Ring *ring, void *buff) +{ + vmw_shmem_read(vmxnet3_ring_curr_cell_pa(ring), buff, ring->cell_size); +} + +static inline void vmxnet3_ring_write_curr_cell(Vmxnet3Ring *ring, void *buff) +{ + vmw_shmem_write(vmxnet3_ring_curr_cell_pa(ring), buff, ring->cell_size); +} + +static inline size_t vmxnet3_ring_curr_cell_idx(Vmxnet3Ring *ring) +{ + return ring->next; +} + +static inline uint8_t vmxnet3_ring_curr_gen(Vmxnet3Ring *ring) +{ + return ring->gen; +} + +/* Debug trace-related functions */ +static inline void +vmxnet3_dump_tx_descr(struct Vmxnet3_TxDesc *descr) +{ + VMW_PKPRN("TX DESCR: " + "addr %" PRIx64 ", len: %d, gen: %d, rsvd: %d, " + "dtype: %d, ext1: %d, msscof: %d, hlen: %d, om: %d, " + "eop: %d, cq: %d, ext2: %d, ti: %d, tci: %d", + le64_to_cpu(descr->addr), descr->len, descr->gen, descr->rsvd, + descr->dtype, descr->ext1, descr->msscof, descr->hlen, descr->om, + descr->eop, descr->cq, descr->ext2, descr->ti, descr->tci); +} + +static inline void +vmxnet3_dump_virt_hdr(struct virtio_net_hdr *vhdr) +{ + VMW_PKPRN("VHDR: flags 0x%x, gso_type: 0x%x, hdr_len: %d, gso_size: %d, " + "csum_start: %d, csum_offset: %d", + vhdr->flags, vhdr->gso_type, vhdr->hdr_len, vhdr->gso_size, + vhdr->csum_start, vhdr->csum_offset); +} + +static inline void +vmxnet3_dump_rx_descr(struct Vmxnet3_RxDesc *descr) +{ + VMW_PKPRN("RX DESCR: addr %" PRIx64 ", len: %d, gen: %d, rsvd: %d, " + "dtype: %d, ext1: %d, btype: %d", + le64_to_cpu(descr->addr), descr->len, descr->gen, + descr->rsvd, descr->dtype, descr->ext1, descr->btype); +} + +/* Device state and helper functions */ +#define VMXNET3_RX_RINGS_PER_QUEUE (2) + +typedef struct { + Vmxnet3Ring tx_ring; + Vmxnet3Ring comp_ring; + + uint8_t intr_idx; + hwaddr tx_stats_pa; + struct UPT1_TxStats txq_stats; +} Vmxnet3TxqDescr; + +typedef struct { + Vmxnet3Ring rx_ring[VMXNET3_RX_RINGS_PER_QUEUE]; + Vmxnet3Ring comp_ring; + uint8_t intr_idx; + hwaddr rx_stats_pa; + struct UPT1_RxStats rxq_stats; +} Vmxnet3RxqDescr; + +typedef struct { + bool is_masked; + bool is_pending; + bool is_asserted; +} Vmxnet3IntState; + +typedef struct { + PCIDevice parent_obj; + NICState *nic; + NICConf conf; + MemoryRegion bar0; + MemoryRegion bar1; + MemoryRegion msix_bar; + + Vmxnet3RxqDescr rxq_descr[VMXNET3_DEVICE_MAX_RX_QUEUES]; + Vmxnet3TxqDescr txq_descr[VMXNET3_DEVICE_MAX_TX_QUEUES]; + + /* Whether MSI-X support was installed successfully */ + bool msix_used; + /* Whether MSI support was installed successfully */ + bool msi_used; + hwaddr drv_shmem; + hwaddr temp_shared_guest_driver_memory; + + uint8_t txq_num; + + /* This boolean tells whether RX packet being indicated has to */ + /* be split into head and body chunks from different RX rings */ + bool rx_packets_compound; + + bool rx_vlan_stripping; + bool lro_supported; + + uint8_t rxq_num; + + /* Network MTU */ + uint32_t mtu; + + /* Maximum number of fragments for indicated TX packets */ + uint32_t max_tx_frags; + + /* Maximum number of fragments for indicated RX packets */ + uint16_t max_rx_frags; + + /* Index for events interrupt */ + uint8_t event_int_idx; + + /* Whether automatic interrupts masking enabled */ + bool auto_int_masking; + + bool peer_has_vhdr; + + /* TX packets to QEMU interface */ + struct VmxnetTxPkt *tx_pkt; + uint32_t offload_mode; + uint32_t cso_or_gso_size; + uint16_t tci; + bool needs_vlan; + + struct VmxnetRxPkt *rx_pkt; + + bool tx_sop; + bool skip_current_tx_pkt; + + uint32_t device_active; + uint32_t last_command; + + uint32_t link_status_and_speed; + + Vmxnet3IntState interrupt_states[VMXNET3_MAX_INTRS]; + + uint32_t temp_mac; /* To store the low part first */ + + MACAddr perm_mac; + uint32_t vlan_table[VMXNET3_VFT_SIZE]; + uint32_t rx_mode; + MACAddr *mcast_list; + uint32_t mcast_list_len; + uint32_t mcast_list_buff_size; /* needed for live migration. */ +} VMXNET3State; + +/* Interrupt management */ + +/* + *This function returns sign whether interrupt line is in asserted state + * This depends on the type of interrupt used. For INTX interrupt line will + * be asserted until explicit deassertion, for MSI(X) interrupt line will + * be deasserted automatically due to notification semantics of the MSI(X) + * interrupts + */ +static bool _vmxnet3_assert_interrupt_line(VMXNET3State *s, uint32_t int_idx) +{ + PCIDevice *d = PCI_DEVICE(s); + + if (s->msix_used && msix_enabled(d)) { + VMW_IRPRN("Sending MSI-X notification for vector %u", int_idx); + msix_notify(d, int_idx); + return false; + } + if (s->msi_used && msi_enabled(d)) { + VMW_IRPRN("Sending MSI notification for vector %u", int_idx); + msi_notify(d, int_idx); + return false; + } + + VMW_IRPRN("Asserting line for interrupt %u", int_idx); + qemu_set_irq(d->irq[int_idx], 1); + return true; +} + +static void _vmxnet3_deassert_interrupt_line(VMXNET3State *s, int lidx) +{ + PCIDevice *d = PCI_DEVICE(s); + + /* + * This function should never be called for MSI(X) interrupts + * because deassertion never required for message interrupts + */ + assert(!s->msix_used || !msix_enabled(d)); + /* + * This function should never be called for MSI(X) interrupts + * because deassertion never required for message interrupts + */ + assert(!s->msi_used || !msi_enabled(d)); + + VMW_IRPRN("Deasserting line for interrupt %u", lidx); + qemu_set_irq(d->irq[lidx], 0); +} + +static void vmxnet3_update_interrupt_line_state(VMXNET3State *s, int lidx) +{ + if (!s->interrupt_states[lidx].is_pending && + s->interrupt_states[lidx].is_asserted) { + VMW_IRPRN("New interrupt line state for index %d is DOWN", lidx); + _vmxnet3_deassert_interrupt_line(s, lidx); + s->interrupt_states[lidx].is_asserted = false; + return; + } + + if (s->interrupt_states[lidx].is_pending && + !s->interrupt_states[lidx].is_masked && + !s->interrupt_states[lidx].is_asserted) { + VMW_IRPRN("New interrupt line state for index %d is UP", lidx); + s->interrupt_states[lidx].is_asserted = + _vmxnet3_assert_interrupt_line(s, lidx); + s->interrupt_states[lidx].is_pending = false; + return; + } +} + +static void vmxnet3_trigger_interrupt(VMXNET3State *s, int lidx) +{ + PCIDevice *d = PCI_DEVICE(s); + s->interrupt_states[lidx].is_pending = true; + vmxnet3_update_interrupt_line_state(s, lidx); + + if (s->msix_used && msix_enabled(d) && s->auto_int_masking) { + goto do_automask; + } + + if (s->msi_used && msi_enabled(d) && s->auto_int_masking) { + goto do_automask; + } + + return; + +do_automask: + s->interrupt_states[lidx].is_masked = true; + vmxnet3_update_interrupt_line_state(s, lidx); +} + +static bool vmxnet3_interrupt_asserted(VMXNET3State *s, int lidx) +{ + return s->interrupt_states[lidx].is_asserted; +} + +static void vmxnet3_clear_interrupt(VMXNET3State *s, int int_idx) +{ + s->interrupt_states[int_idx].is_pending = false; + if (s->auto_int_masking) { + s->interrupt_states[int_idx].is_masked = true; + } + vmxnet3_update_interrupt_line_state(s, int_idx); +} + +static void +vmxnet3_on_interrupt_mask_changed(VMXNET3State *s, int lidx, bool is_masked) +{ + s->interrupt_states[lidx].is_masked = is_masked; + vmxnet3_update_interrupt_line_state(s, lidx); +} + +static bool vmxnet3_verify_driver_magic(hwaddr dshmem) +{ + return (VMXNET3_READ_DRV_SHARED32(dshmem, magic) == VMXNET3_REV1_MAGIC); +} + +#define VMXNET3_GET_BYTE(x, byte_num) (((x) >> (byte_num)*8) & 0xFF) +#define VMXNET3_MAKE_BYTE(byte_num, val) \ + (((uint32_t)((val) & 0xFF)) << (byte_num)*8) + +static void vmxnet3_set_variable_mac(VMXNET3State *s, uint32_t h, uint32_t l) +{ + s->conf.macaddr.a[0] = VMXNET3_GET_BYTE(l, 0); + s->conf.macaddr.a[1] = VMXNET3_GET_BYTE(l, 1); + s->conf.macaddr.a[2] = VMXNET3_GET_BYTE(l, 2); + s->conf.macaddr.a[3] = VMXNET3_GET_BYTE(l, 3); + s->conf.macaddr.a[4] = VMXNET3_GET_BYTE(h, 0); + s->conf.macaddr.a[5] = VMXNET3_GET_BYTE(h, 1); + + VMW_CFPRN("Variable MAC: " VMXNET_MF, VMXNET_MA(s->conf.macaddr.a)); + + qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); +} + +static uint64_t vmxnet3_get_mac_low(MACAddr *addr) +{ + return VMXNET3_MAKE_BYTE(0, addr->a[0]) | + VMXNET3_MAKE_BYTE(1, addr->a[1]) | + VMXNET3_MAKE_BYTE(2, addr->a[2]) | + VMXNET3_MAKE_BYTE(3, addr->a[3]); +} + +static uint64_t vmxnet3_get_mac_high(MACAddr *addr) +{ + return VMXNET3_MAKE_BYTE(0, addr->a[4]) | + VMXNET3_MAKE_BYTE(1, addr->a[5]); +} + +static void +vmxnet3_inc_tx_consumption_counter(VMXNET3State *s, int qidx) +{ + vmxnet3_ring_inc(&s->txq_descr[qidx].tx_ring); +} + +static inline void +vmxnet3_inc_rx_consumption_counter(VMXNET3State *s, int qidx, int ridx) +{ + vmxnet3_ring_inc(&s->rxq_descr[qidx].rx_ring[ridx]); +} + +static inline void +vmxnet3_inc_tx_completion_counter(VMXNET3State *s, int qidx) +{ + vmxnet3_ring_inc(&s->txq_descr[qidx].comp_ring); +} + +static void +vmxnet3_inc_rx_completion_counter(VMXNET3State *s, int qidx) +{ + vmxnet3_ring_inc(&s->rxq_descr[qidx].comp_ring); +} + +static void +vmxnet3_dec_rx_completion_counter(VMXNET3State *s, int qidx) +{ + vmxnet3_ring_dec(&s->rxq_descr[qidx].comp_ring); +} + +static void vmxnet3_complete_packet(VMXNET3State *s, int qidx, uint32 tx_ridx) +{ + struct Vmxnet3_TxCompDesc txcq_descr; + + VMXNET3_RING_DUMP(VMW_RIPRN, "TXC", qidx, &s->txq_descr[qidx].comp_ring); + + txcq_descr.txdIdx = tx_ridx; + txcq_descr.gen = vmxnet3_ring_curr_gen(&s->txq_descr[qidx].comp_ring); + + vmxnet3_ring_write_curr_cell(&s->txq_descr[qidx].comp_ring, &txcq_descr); + + /* Flush changes in TX descriptor before changing the counter value */ + smp_wmb(); + + vmxnet3_inc_tx_completion_counter(s, qidx); + vmxnet3_trigger_interrupt(s, s->txq_descr[qidx].intr_idx); +} + +static bool +vmxnet3_setup_tx_offloads(VMXNET3State *s) +{ + switch (s->offload_mode) { + case VMXNET3_OM_NONE: + vmxnet_tx_pkt_build_vheader(s->tx_pkt, false, false, 0); + break; + + case VMXNET3_OM_CSUM: + vmxnet_tx_pkt_build_vheader(s->tx_pkt, false, true, 0); + VMW_PKPRN("L4 CSO requested\n"); + break; + + case VMXNET3_OM_TSO: + vmxnet_tx_pkt_build_vheader(s->tx_pkt, true, true, + s->cso_or_gso_size); + vmxnet_tx_pkt_update_ip_checksums(s->tx_pkt); + VMW_PKPRN("GSO offload requested."); + break; + + default: + assert(false); + return false; + } + + return true; +} + +static void +vmxnet3_tx_retrieve_metadata(VMXNET3State *s, + const struct Vmxnet3_TxDesc *txd) +{ + s->offload_mode = txd->om; + s->cso_or_gso_size = txd->msscof; + s->tci = txd->tci; + s->needs_vlan = txd->ti; +} + +typedef enum { + VMXNET3_PKT_STATUS_OK, + VMXNET3_PKT_STATUS_ERROR, + VMXNET3_PKT_STATUS_DISCARD,/* only for tx */ + VMXNET3_PKT_STATUS_OUT_OF_BUF /* only for rx */ +} Vmxnet3PktStatus; + +static void +vmxnet3_on_tx_done_update_stats(VMXNET3State *s, int qidx, + Vmxnet3PktStatus status) +{ + size_t tot_len = vmxnet_tx_pkt_get_total_len(s->tx_pkt); + struct UPT1_TxStats *stats = &s->txq_descr[qidx].txq_stats; + + switch (status) { + case VMXNET3_PKT_STATUS_OK: + switch (vmxnet_tx_pkt_get_packet_type(s->tx_pkt)) { + case ETH_PKT_BCAST: + stats->bcastPktsTxOK++; + stats->bcastBytesTxOK += tot_len; + break; + case ETH_PKT_MCAST: + stats->mcastPktsTxOK++; + stats->mcastBytesTxOK += tot_len; + break; + case ETH_PKT_UCAST: + stats->ucastPktsTxOK++; + stats->ucastBytesTxOK += tot_len; + break; + default: + assert(false); + } + + if (s->offload_mode == VMXNET3_OM_TSO) { + /* + * According to VMWARE headers this statistic is a number + * of packets after segmentation but since we don't have + * this information in QEMU model, the best we can do is to + * provide number of non-segmented packets + */ + stats->TSOPktsTxOK++; + stats->TSOBytesTxOK += tot_len; + } + break; + + case VMXNET3_PKT_STATUS_DISCARD: + stats->pktsTxDiscard++; + break; + + case VMXNET3_PKT_STATUS_ERROR: + stats->pktsTxError++; + break; + + default: + assert(false); + } +} + +static void +vmxnet3_on_rx_done_update_stats(VMXNET3State *s, + int qidx, + Vmxnet3PktStatus status) +{ + struct UPT1_RxStats *stats = &s->rxq_descr[qidx].rxq_stats; + size_t tot_len = vmxnet_rx_pkt_get_total_len(s->rx_pkt); + + switch (status) { + case VMXNET3_PKT_STATUS_OUT_OF_BUF: + stats->pktsRxOutOfBuf++; + break; + + case VMXNET3_PKT_STATUS_ERROR: + stats->pktsRxError++; + break; + case VMXNET3_PKT_STATUS_OK: + switch (vmxnet_rx_pkt_get_packet_type(s->rx_pkt)) { + case ETH_PKT_BCAST: + stats->bcastPktsRxOK++; + stats->bcastBytesRxOK += tot_len; + break; + case ETH_PKT_MCAST: + stats->mcastPktsRxOK++; + stats->mcastBytesRxOK += tot_len; + break; + case ETH_PKT_UCAST: + stats->ucastPktsRxOK++; + stats->ucastBytesRxOK += tot_len; + break; + default: + assert(false); + } + + if (tot_len > s->mtu) { + stats->LROPktsRxOK++; + stats->LROBytesRxOK += tot_len; + } + break; + default: + assert(false); + } +} + +static inline bool +vmxnet3_pop_next_tx_descr(VMXNET3State *s, + int qidx, + struct Vmxnet3_TxDesc *txd, + uint32_t *descr_idx) +{ + Vmxnet3Ring *ring = &s->txq_descr[qidx].tx_ring; + + vmxnet3_ring_read_curr_cell(ring, txd); + if (txd->gen == vmxnet3_ring_curr_gen(ring)) { + /* Only read after generation field verification */ + smp_rmb(); + /* Re-read to be sure we got the latest version */ + vmxnet3_ring_read_curr_cell(ring, txd); + VMXNET3_RING_DUMP(VMW_RIPRN, "TX", qidx, ring); + *descr_idx = vmxnet3_ring_curr_cell_idx(ring); + vmxnet3_inc_tx_consumption_counter(s, qidx); + return true; + } + + return false; +} + +static bool +vmxnet3_send_packet(VMXNET3State *s, uint32_t qidx) +{ + Vmxnet3PktStatus status = VMXNET3_PKT_STATUS_OK; + + if (!vmxnet3_setup_tx_offloads(s)) { + status = VMXNET3_PKT_STATUS_ERROR; + goto func_exit; + } + + /* debug prints */ + vmxnet3_dump_virt_hdr(vmxnet_tx_pkt_get_vhdr(s->tx_pkt)); + vmxnet_tx_pkt_dump(s->tx_pkt); + + if (!vmxnet_tx_pkt_send(s->tx_pkt, qemu_get_queue(s->nic))) { + status = VMXNET3_PKT_STATUS_DISCARD; + goto func_exit; + } + +func_exit: + vmxnet3_on_tx_done_update_stats(s, qidx, status); + return (status == VMXNET3_PKT_STATUS_OK); +} + +static void vmxnet3_process_tx_queue(VMXNET3State *s, int qidx) +{ + struct Vmxnet3_TxDesc txd; + uint32_t txd_idx; + uint32_t data_len; + hwaddr data_pa; + + for (;;) { + if (!vmxnet3_pop_next_tx_descr(s, qidx, &txd, &txd_idx)) { + break; + } + + vmxnet3_dump_tx_descr(&txd); + + if (!s->skip_current_tx_pkt) { + data_len = (txd.len > 0) ? txd.len : VMXNET3_MAX_TX_BUF_SIZE; + data_pa = le64_to_cpu(txd.addr); + + if (!vmxnet_tx_pkt_add_raw_fragment(s->tx_pkt, + data_pa, + data_len)) { + s->skip_current_tx_pkt = true; + } + } + + if (s->tx_sop) { + vmxnet3_tx_retrieve_metadata(s, &txd); + s->tx_sop = false; + } + + if (txd.eop) { + if (!s->skip_current_tx_pkt) { + vmxnet_tx_pkt_parse(s->tx_pkt); + + if (s->needs_vlan) { + vmxnet_tx_pkt_setup_vlan_header(s->tx_pkt, s->tci); + } + + vmxnet3_send_packet(s, qidx); + } else { + vmxnet3_on_tx_done_update_stats(s, qidx, + VMXNET3_PKT_STATUS_ERROR); + } + + vmxnet3_complete_packet(s, qidx, txd_idx); + s->tx_sop = true; + s->skip_current_tx_pkt = false; + vmxnet_tx_pkt_reset(s->tx_pkt); + } + } +} + +static inline void +vmxnet3_read_next_rx_descr(VMXNET3State *s, int qidx, int ridx, + struct Vmxnet3_RxDesc *dbuf, uint32_t *didx) +{ + Vmxnet3Ring *ring = &s->rxq_descr[qidx].rx_ring[ridx]; + *didx = vmxnet3_ring_curr_cell_idx(ring); + vmxnet3_ring_read_curr_cell(ring, dbuf); +} + +static inline uint8_t +vmxnet3_get_rx_ring_gen(VMXNET3State *s, int qidx, int ridx) +{ + return s->rxq_descr[qidx].rx_ring[ridx].gen; +} + +static inline hwaddr +vmxnet3_pop_rxc_descr(VMXNET3State *s, int qidx, uint32_t *descr_gen) +{ + uint8_t ring_gen; + struct Vmxnet3_RxCompDesc rxcd; + + hwaddr daddr = + vmxnet3_ring_curr_cell_pa(&s->rxq_descr[qidx].comp_ring); + + cpu_physical_memory_read(daddr, &rxcd, sizeof(struct Vmxnet3_RxCompDesc)); + ring_gen = vmxnet3_ring_curr_gen(&s->rxq_descr[qidx].comp_ring); + + if (rxcd.gen != ring_gen) { + *descr_gen = ring_gen; + vmxnet3_inc_rx_completion_counter(s, qidx); + return daddr; + } + + return 0; +} + +static inline void +vmxnet3_revert_rxc_descr(VMXNET3State *s, int qidx) +{ + vmxnet3_dec_rx_completion_counter(s, qidx); +} + +#define RXQ_IDX (0) +#define RX_HEAD_BODY_RING (0) +#define RX_BODY_ONLY_RING (1) + +static bool +vmxnet3_get_next_head_rx_descr(VMXNET3State *s, + struct Vmxnet3_RxDesc *descr_buf, + uint32_t *descr_idx, + uint32_t *ridx) +{ + for (;;) { + uint32_t ring_gen; + vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_HEAD_BODY_RING, + descr_buf, descr_idx); + + /* If no more free descriptors - return */ + ring_gen = vmxnet3_get_rx_ring_gen(s, RXQ_IDX, RX_HEAD_BODY_RING); + if (descr_buf->gen != ring_gen) { + return false; + } + + /* Only read after generation field verification */ + smp_rmb(); + /* Re-read to be sure we got the latest version */ + vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_HEAD_BODY_RING, + descr_buf, descr_idx); + + /* Mark current descriptor as used/skipped */ + vmxnet3_inc_rx_consumption_counter(s, RXQ_IDX, RX_HEAD_BODY_RING); + + /* If this is what we are looking for - return */ + if (descr_buf->btype == VMXNET3_RXD_BTYPE_HEAD) { + *ridx = RX_HEAD_BODY_RING; + return true; + } + } +} + +static bool +vmxnet3_get_next_body_rx_descr(VMXNET3State *s, + struct Vmxnet3_RxDesc *d, + uint32_t *didx, + uint32_t *ridx) +{ + vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_HEAD_BODY_RING, d, didx); + + /* Try to find corresponding descriptor in head/body ring */ + if (d->gen == vmxnet3_get_rx_ring_gen(s, RXQ_IDX, RX_HEAD_BODY_RING)) { + /* Only read after generation field verification */ + smp_rmb(); + /* Re-read to be sure we got the latest version */ + vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_HEAD_BODY_RING, d, didx); + if (d->btype == VMXNET3_RXD_BTYPE_BODY) { + vmxnet3_inc_rx_consumption_counter(s, RXQ_IDX, RX_HEAD_BODY_RING); + *ridx = RX_HEAD_BODY_RING; + return true; + } + } + + /* + * If there is no free descriptors on head/body ring or next free + * descriptor is a head descriptor switch to body only ring + */ + vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_BODY_ONLY_RING, d, didx); + + /* If no more free descriptors - return */ + if (d->gen == vmxnet3_get_rx_ring_gen(s, RXQ_IDX, RX_BODY_ONLY_RING)) { + /* Only read after generation field verification */ + smp_rmb(); + /* Re-read to be sure we got the latest version */ + vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_BODY_ONLY_RING, d, didx); + assert(d->btype == VMXNET3_RXD_BTYPE_BODY); + *ridx = RX_BODY_ONLY_RING; + vmxnet3_inc_rx_consumption_counter(s, RXQ_IDX, RX_BODY_ONLY_RING); + return true; + } + + return false; +} + +static inline bool +vmxnet3_get_next_rx_descr(VMXNET3State *s, bool is_head, + struct Vmxnet3_RxDesc *descr_buf, + uint32_t *descr_idx, + uint32_t *ridx) +{ + if (is_head || !s->rx_packets_compound) { + return vmxnet3_get_next_head_rx_descr(s, descr_buf, descr_idx, ridx); + } else { + return vmxnet3_get_next_body_rx_descr(s, descr_buf, descr_idx, ridx); + } +} + +static void vmxnet3_rx_update_descr(struct VmxnetRxPkt *pkt, + struct Vmxnet3_RxCompDesc *rxcd) +{ + int csum_ok, is_gso; + bool isip4, isip6, istcp, isudp; + struct virtio_net_hdr *vhdr; + uint8_t offload_type; + + if (vmxnet_rx_pkt_is_vlan_stripped(pkt)) { + rxcd->ts = 1; + rxcd->tci = vmxnet_rx_pkt_get_vlan_tag(pkt); + } + + if (!vmxnet_rx_pkt_has_virt_hdr(pkt)) { + goto nocsum; + } + + vhdr = vmxnet_rx_pkt_get_vhdr(pkt); + /* + * Checksum is valid when lower level tell so or when lower level + * requires checksum offload telling that packet produced/bridged + * locally and did travel over network after last checksum calculation + * or production + */ + csum_ok = VMXNET_FLAG_IS_SET(vhdr->flags, VIRTIO_NET_HDR_F_DATA_VALID) || + VMXNET_FLAG_IS_SET(vhdr->flags, VIRTIO_NET_HDR_F_NEEDS_CSUM); + + offload_type = vhdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN; + is_gso = (offload_type != VIRTIO_NET_HDR_GSO_NONE) ? 1 : 0; + + if (!csum_ok && !is_gso) { + goto nocsum; + } + + vmxnet_rx_pkt_get_protocols(pkt, &isip4, &isip6, &isudp, &istcp); + if ((!istcp && !isudp) || (!isip4 && !isip6)) { + goto nocsum; + } + + rxcd->cnc = 0; + rxcd->v4 = isip4 ? 1 : 0; + rxcd->v6 = isip6 ? 1 : 0; + rxcd->tcp = istcp ? 1 : 0; + rxcd->udp = isudp ? 1 : 0; + rxcd->fcs = rxcd->tuc = rxcd->ipc = 1; + return; + +nocsum: + rxcd->cnc = 1; + return; +} + +static void +vmxnet3_physical_memory_writev(const struct iovec *iov, + size_t start_iov_off, + hwaddr target_addr, + size_t bytes_to_copy) +{ + size_t curr_off = 0; + size_t copied = 0; + + while (bytes_to_copy) { + if (start_iov_off < (curr_off + iov->iov_len)) { + size_t chunk_len = + MIN((curr_off + iov->iov_len) - start_iov_off, bytes_to_copy); + + cpu_physical_memory_write(target_addr + copied, + iov->iov_base + start_iov_off - curr_off, + chunk_len); + + copied += chunk_len; + start_iov_off += chunk_len; + curr_off = start_iov_off; + bytes_to_copy -= chunk_len; + } else { + curr_off += iov->iov_len; + } + iov++; + } +} + +static bool +vmxnet3_indicate_packet(VMXNET3State *s) +{ + struct Vmxnet3_RxDesc rxd; + bool is_head = true; + uint32_t rxd_idx; + uint32_t rx_ridx; + + struct Vmxnet3_RxCompDesc rxcd; + uint32_t new_rxcd_gen = VMXNET3_INIT_GEN; + hwaddr new_rxcd_pa = 0; + hwaddr ready_rxcd_pa = 0; + struct iovec *data = vmxnet_rx_pkt_get_iovec(s->rx_pkt); + size_t bytes_copied = 0; + size_t bytes_left = vmxnet_rx_pkt_get_total_len(s->rx_pkt); + uint16_t num_frags = 0; + size_t chunk_size; + + vmxnet_rx_pkt_dump(s->rx_pkt); + + while (bytes_left > 0) { + + /* cannot add more frags to packet */ + if (num_frags == s->max_rx_frags) { + break; + } + + new_rxcd_pa = vmxnet3_pop_rxc_descr(s, RXQ_IDX, &new_rxcd_gen); + if (!new_rxcd_pa) { + break; + } + + if (!vmxnet3_get_next_rx_descr(s, is_head, &rxd, &rxd_idx, &rx_ridx)) { + break; + } + + chunk_size = MIN(bytes_left, rxd.len); + vmxnet3_physical_memory_writev(data, bytes_copied, + le64_to_cpu(rxd.addr), chunk_size); + bytes_copied += chunk_size; + bytes_left -= chunk_size; + + vmxnet3_dump_rx_descr(&rxd); + + if (0 != ready_rxcd_pa) { + cpu_physical_memory_write(ready_rxcd_pa, &rxcd, sizeof(rxcd)); + } + + memset(&rxcd, 0, sizeof(struct Vmxnet3_RxCompDesc)); + rxcd.rxdIdx = rxd_idx; + rxcd.len = chunk_size; + rxcd.sop = is_head; + rxcd.gen = new_rxcd_gen; + rxcd.rqID = RXQ_IDX + rx_ridx * s->rxq_num; + + if (0 == bytes_left) { + vmxnet3_rx_update_descr(s->rx_pkt, &rxcd); + } + + VMW_RIPRN("RX Completion descriptor: rxRing: %lu rxIdx %lu len %lu " + "sop %d csum_correct %lu", + (unsigned long) rx_ridx, + (unsigned long) rxcd.rxdIdx, + (unsigned long) rxcd.len, + (int) rxcd.sop, + (unsigned long) rxcd.tuc); + + is_head = false; + ready_rxcd_pa = new_rxcd_pa; + new_rxcd_pa = 0; + } + + if (0 != ready_rxcd_pa) { + rxcd.eop = 1; + rxcd.err = (0 != bytes_left); + cpu_physical_memory_write(ready_rxcd_pa, &rxcd, sizeof(rxcd)); + + /* Flush RX descriptor changes */ + smp_wmb(); + } + + if (0 != new_rxcd_pa) { + vmxnet3_revert_rxc_descr(s, RXQ_IDX); + } + + vmxnet3_trigger_interrupt(s, s->rxq_descr[RXQ_IDX].intr_idx); + + if (bytes_left == 0) { + vmxnet3_on_rx_done_update_stats(s, RXQ_IDX, VMXNET3_PKT_STATUS_OK); + return true; + } else if (num_frags == s->max_rx_frags) { + vmxnet3_on_rx_done_update_stats(s, RXQ_IDX, VMXNET3_PKT_STATUS_ERROR); + return false; + } else { + vmxnet3_on_rx_done_update_stats(s, RXQ_IDX, + VMXNET3_PKT_STATUS_OUT_OF_BUF); + return false; + } +} + +static void +vmxnet3_io_bar0_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + VMXNET3State *s = opaque; + + if (VMW_IS_MULTIREG_ADDR(addr, VMXNET3_REG_TXPROD, + VMXNET3_DEVICE_MAX_TX_QUEUES, VMXNET3_REG_ALIGN)) { + int tx_queue_idx = + VMW_MULTIREG_IDX_BY_ADDR(addr, VMXNET3_REG_TXPROD, + VMXNET3_REG_ALIGN); + assert(tx_queue_idx <= s->txq_num); + vmxnet3_process_tx_queue(s, tx_queue_idx); + return; + } + + if (VMW_IS_MULTIREG_ADDR(addr, VMXNET3_REG_IMR, + VMXNET3_MAX_INTRS, VMXNET3_REG_ALIGN)) { + int l = VMW_MULTIREG_IDX_BY_ADDR(addr, VMXNET3_REG_IMR, + VMXNET3_REG_ALIGN); + + VMW_CBPRN("Interrupt mask for line %d written: 0x%" PRIx64, l, val); + + vmxnet3_on_interrupt_mask_changed(s, l, val); + return; + } + + if (VMW_IS_MULTIREG_ADDR(addr, VMXNET3_REG_RXPROD, + VMXNET3_DEVICE_MAX_RX_QUEUES, VMXNET3_REG_ALIGN) || + VMW_IS_MULTIREG_ADDR(addr, VMXNET3_REG_RXPROD2, + VMXNET3_DEVICE_MAX_RX_QUEUES, VMXNET3_REG_ALIGN)) { + return; + } + + VMW_WRPRN("BAR0 unknown write [%" PRIx64 "] = %" PRIx64 ", size %d", + (uint64_t) addr, val, size); +} + +static uint64_t +vmxnet3_io_bar0_read(void *opaque, hwaddr addr, unsigned size) +{ + if (VMW_IS_MULTIREG_ADDR(addr, VMXNET3_REG_IMR, + VMXNET3_MAX_INTRS, VMXNET3_REG_ALIGN)) { + assert(false); + } + + VMW_CBPRN("BAR0 unknown read [%" PRIx64 "], size %d", addr, size); + return 0; +} + +static void vmxnet3_reset_interrupt_states(VMXNET3State *s) +{ + int i; + for (i = 0; i < ARRAY_SIZE(s->interrupt_states); i++) { + s->interrupt_states[i].is_asserted = false; + s->interrupt_states[i].is_pending = false; + s->interrupt_states[i].is_masked = true; + } +} + +static void vmxnet3_reset_mac(VMXNET3State *s) +{ + memcpy(&s->conf.macaddr.a, &s->perm_mac.a, sizeof(s->perm_mac.a)); + VMW_CFPRN("MAC address set to: " VMXNET_MF, VMXNET_MA(s->conf.macaddr.a)); +} + +static void vmxnet3_deactivate_device(VMXNET3State *s) +{ + VMW_CBPRN("Deactivating vmxnet3..."); + s->device_active = false; +} + +static void vmxnet3_reset(VMXNET3State *s) +{ + VMW_CBPRN("Resetting vmxnet3..."); + + vmxnet3_deactivate_device(s); + vmxnet3_reset_interrupt_states(s); + vmxnet_tx_pkt_reset(s->tx_pkt); + s->drv_shmem = 0; + s->tx_sop = true; + s->skip_current_tx_pkt = false; +} + +static void vmxnet3_update_rx_mode(VMXNET3State *s) +{ + s->rx_mode = VMXNET3_READ_DRV_SHARED32(s->drv_shmem, + devRead.rxFilterConf.rxMode); + VMW_CFPRN("RX mode: 0x%08X", s->rx_mode); +} + +static void vmxnet3_update_vlan_filters(VMXNET3State *s) +{ + int i; + + /* Copy configuration from shared memory */ + VMXNET3_READ_DRV_SHARED(s->drv_shmem, + devRead.rxFilterConf.vfTable, + s->vlan_table, + sizeof(s->vlan_table)); + + /* Invert byte order when needed */ + for (i = 0; i < ARRAY_SIZE(s->vlan_table); i++) { + s->vlan_table[i] = le32_to_cpu(s->vlan_table[i]); + } + + /* Dump configuration for debugging purposes */ + VMW_CFPRN("Configured VLANs:"); + for (i = 0; i < sizeof(s->vlan_table) * 8; i++) { + if (VMXNET3_VFTABLE_ENTRY_IS_SET(s->vlan_table, i)) { + VMW_CFPRN("\tVLAN %d is present", i); + } + } +} + +static void vmxnet3_update_mcast_filters(VMXNET3State *s) +{ + uint16_t list_bytes = + VMXNET3_READ_DRV_SHARED16(s->drv_shmem, + devRead.rxFilterConf.mfTableLen); + + s->mcast_list_len = list_bytes / sizeof(s->mcast_list[0]); + + s->mcast_list = g_realloc(s->mcast_list, list_bytes); + if (NULL == s->mcast_list) { + if (0 == s->mcast_list_len) { + VMW_CFPRN("Current multicast list is empty"); + } else { + VMW_ERPRN("Failed to allocate multicast list of %d elements", + s->mcast_list_len); + } + s->mcast_list_len = 0; + } else { + int i; + hwaddr mcast_list_pa = + VMXNET3_READ_DRV_SHARED64(s->drv_shmem, + devRead.rxFilterConf.mfTablePA); + + cpu_physical_memory_read(mcast_list_pa, s->mcast_list, list_bytes); + VMW_CFPRN("Current multicast list len is %d:", s->mcast_list_len); + for (i = 0; i < s->mcast_list_len; i++) { + VMW_CFPRN("\t" VMXNET_MF, VMXNET_MA(s->mcast_list[i].a)); + } + } +} + +static void vmxnet3_setup_rx_filtering(VMXNET3State *s) +{ + vmxnet3_update_rx_mode(s); + vmxnet3_update_vlan_filters(s); + vmxnet3_update_mcast_filters(s); +} + +static uint32_t vmxnet3_get_interrupt_config(VMXNET3State *s) +{ + uint32_t interrupt_mode = VMXNET3_IT_AUTO | (VMXNET3_IMM_AUTO << 2); + VMW_CFPRN("Interrupt config is 0x%X", interrupt_mode); + return interrupt_mode; +} + +static void vmxnet3_fill_stats(VMXNET3State *s) +{ + int i; + for (i = 0; i < s->txq_num; i++) { + cpu_physical_memory_write(s->txq_descr[i].tx_stats_pa, + &s->txq_descr[i].txq_stats, + sizeof(s->txq_descr[i].txq_stats)); + } + + for (i = 0; i < s->rxq_num; i++) { + cpu_physical_memory_write(s->rxq_descr[i].rx_stats_pa, + &s->rxq_descr[i].rxq_stats, + sizeof(s->rxq_descr[i].rxq_stats)); + } +} + +static void vmxnet3_adjust_by_guest_type(VMXNET3State *s) +{ + struct Vmxnet3_GOSInfo gos; + + VMXNET3_READ_DRV_SHARED(s->drv_shmem, devRead.misc.driverInfo.gos, + &gos, sizeof(gos)); + s->rx_packets_compound = + (gos.gosType == VMXNET3_GOS_TYPE_WIN) ? false : true; + + VMW_CFPRN("Guest type specifics: RXCOMPOUND: %d", s->rx_packets_compound); +} + +static void +vmxnet3_dump_conf_descr(const char *name, + struct Vmxnet3_VariableLenConfDesc *pm_descr) +{ + VMW_CFPRN("%s descriptor dump: Version %u, Length %u", + name, pm_descr->confVer, pm_descr->confLen); + +}; + +static void vmxnet3_update_pm_state(VMXNET3State *s) +{ + struct Vmxnet3_VariableLenConfDesc pm_descr; + + pm_descr.confLen = + VMXNET3_READ_DRV_SHARED32(s->drv_shmem, devRead.pmConfDesc.confLen); + pm_descr.confVer = + VMXNET3_READ_DRV_SHARED32(s->drv_shmem, devRead.pmConfDesc.confVer); + pm_descr.confPA = + VMXNET3_READ_DRV_SHARED64(s->drv_shmem, devRead.pmConfDesc.confPA); + + vmxnet3_dump_conf_descr("PM State", &pm_descr); +} + +static void vmxnet3_update_features(VMXNET3State *s) +{ + uint32_t guest_features; + int rxcso_supported; + + guest_features = VMXNET3_READ_DRV_SHARED32(s->drv_shmem, + devRead.misc.uptFeatures); + + rxcso_supported = VMXNET_FLAG_IS_SET(guest_features, UPT1_F_RXCSUM); + s->rx_vlan_stripping = VMXNET_FLAG_IS_SET(guest_features, UPT1_F_RXVLAN); + s->lro_supported = VMXNET_FLAG_IS_SET(guest_features, UPT1_F_LRO); + + VMW_CFPRN("Features configuration: LRO: %d, RXCSUM: %d, VLANSTRIP: %d", + s->lro_supported, rxcso_supported, + s->rx_vlan_stripping); + if (s->peer_has_vhdr) { + tap_set_offload(qemu_get_queue(s->nic)->peer, + rxcso_supported, + s->lro_supported, + s->lro_supported, + 0, + 0); + } +} + +static void vmxnet3_activate_device(VMXNET3State *s) +{ + int i; + static const uint32_t VMXNET3_DEF_TX_THRESHOLD = 1; + hwaddr qdescr_table_pa; + uint64_t pa; + uint32_t size; + + /* Verify configuration consistency */ + if (!vmxnet3_verify_driver_magic(s->drv_shmem)) { + VMW_ERPRN("Device configuration received from driver is invalid"); + return; + } + + vmxnet3_adjust_by_guest_type(s); + vmxnet3_update_features(s); + vmxnet3_update_pm_state(s); + vmxnet3_setup_rx_filtering(s); + /* Cache fields from shared memory */ + s->mtu = VMXNET3_READ_DRV_SHARED32(s->drv_shmem, devRead.misc.mtu); + VMW_CFPRN("MTU is %u", s->mtu); + + s->max_rx_frags = + VMXNET3_READ_DRV_SHARED16(s->drv_shmem, devRead.misc.maxNumRxSG); + + VMW_CFPRN("Max RX fragments is %u", s->max_rx_frags); + + s->event_int_idx = + VMXNET3_READ_DRV_SHARED8(s->drv_shmem, devRead.intrConf.eventIntrIdx); + VMW_CFPRN("Events interrupt line is %u", s->event_int_idx); + + s->auto_int_masking = + VMXNET3_READ_DRV_SHARED8(s->drv_shmem, devRead.intrConf.autoMask); + VMW_CFPRN("Automatic interrupt masking is %d", (int)s->auto_int_masking); + + s->txq_num = + VMXNET3_READ_DRV_SHARED8(s->drv_shmem, devRead.misc.numTxQueues); + s->rxq_num = + VMXNET3_READ_DRV_SHARED8(s->drv_shmem, devRead.misc.numRxQueues); + + VMW_CFPRN("Number of TX/RX queues %u/%u", s->txq_num, s->rxq_num); + assert(s->txq_num <= VMXNET3_DEVICE_MAX_TX_QUEUES); + + qdescr_table_pa = + VMXNET3_READ_DRV_SHARED64(s->drv_shmem, devRead.misc.queueDescPA); + VMW_CFPRN("TX queues descriptors table is at 0x%" PRIx64, qdescr_table_pa); + + /* + * Worst-case scenario is a packet that holds all TX rings space so + * we calculate total size of all TX rings for max TX fragments number + */ + s->max_tx_frags = 0; + + /* TX queues */ + for (i = 0; i < s->txq_num; i++) { + hwaddr qdescr_pa = + qdescr_table_pa + i * sizeof(struct Vmxnet3_TxQueueDesc); + + /* Read interrupt number for this TX queue */ + s->txq_descr[i].intr_idx = + VMXNET3_READ_TX_QUEUE_DESCR8(qdescr_pa, conf.intrIdx); + + VMW_CFPRN("TX Queue %d interrupt: %d", i, s->txq_descr[i].intr_idx); + + /* Read rings memory locations for TX queues */ + pa = VMXNET3_READ_TX_QUEUE_DESCR64(qdescr_pa, conf.txRingBasePA); + size = VMXNET3_READ_TX_QUEUE_DESCR32(qdescr_pa, conf.txRingSize); + + vmxnet3_ring_init(&s->txq_descr[i].tx_ring, pa, size, + sizeof(struct Vmxnet3_TxDesc), false); + VMXNET3_RING_DUMP(VMW_CFPRN, "TX", i, &s->txq_descr[i].tx_ring); + + s->max_tx_frags += size; + + /* TXC ring */ + pa = VMXNET3_READ_TX_QUEUE_DESCR64(qdescr_pa, conf.compRingBasePA); + size = VMXNET3_READ_TX_QUEUE_DESCR32(qdescr_pa, conf.compRingSize); + vmxnet3_ring_init(&s->txq_descr[i].comp_ring, pa, size, + sizeof(struct Vmxnet3_TxCompDesc), true); + VMXNET3_RING_DUMP(VMW_CFPRN, "TXC", i, &s->txq_descr[i].comp_ring); + + s->txq_descr[i].tx_stats_pa = + qdescr_pa + offsetof(struct Vmxnet3_TxQueueDesc, stats); + + memset(&s->txq_descr[i].txq_stats, 0, + sizeof(s->txq_descr[i].txq_stats)); + + /* Fill device-managed parameters for queues */ + VMXNET3_WRITE_TX_QUEUE_DESCR32(qdescr_pa, + ctrl.txThreshold, + VMXNET3_DEF_TX_THRESHOLD); + } + + /* Preallocate TX packet wrapper */ + VMW_CFPRN("Max TX fragments is %u", s->max_tx_frags); + vmxnet_tx_pkt_init(&s->tx_pkt, s->max_tx_frags, s->peer_has_vhdr); + vmxnet_rx_pkt_init(&s->rx_pkt, s->peer_has_vhdr); + + /* Read rings memory locations for RX queues */ + for (i = 0; i < s->rxq_num; i++) { + int j; + hwaddr qd_pa = + qdescr_table_pa + s->txq_num * sizeof(struct Vmxnet3_TxQueueDesc) + + i * sizeof(struct Vmxnet3_RxQueueDesc); + + /* Read interrupt number for this RX queue */ + s->rxq_descr[i].intr_idx = + VMXNET3_READ_TX_QUEUE_DESCR8(qd_pa, conf.intrIdx); + + VMW_CFPRN("RX Queue %d interrupt: %d", i, s->rxq_descr[i].intr_idx); + + /* Read rings memory locations */ + for (j = 0; j < VMXNET3_RX_RINGS_PER_QUEUE; j++) { + /* RX rings */ + pa = VMXNET3_READ_RX_QUEUE_DESCR64(qd_pa, conf.rxRingBasePA[j]); + size = VMXNET3_READ_RX_QUEUE_DESCR32(qd_pa, conf.rxRingSize[j]); + vmxnet3_ring_init(&s->rxq_descr[i].rx_ring[j], pa, size, + sizeof(struct Vmxnet3_RxDesc), false); + VMW_CFPRN("RX queue %d:%d: Base: %" PRIx64 ", Size: %d", + i, j, pa, size); + } + + /* RXC ring */ + pa = VMXNET3_READ_RX_QUEUE_DESCR64(qd_pa, conf.compRingBasePA); + size = VMXNET3_READ_RX_QUEUE_DESCR32(qd_pa, conf.compRingSize); + vmxnet3_ring_init(&s->rxq_descr[i].comp_ring, pa, size, + sizeof(struct Vmxnet3_RxCompDesc), true); + VMW_CFPRN("RXC queue %d: Base: %" PRIx64 ", Size: %d", i, pa, size); + + s->rxq_descr[i].rx_stats_pa = + qd_pa + offsetof(struct Vmxnet3_RxQueueDesc, stats); + memset(&s->rxq_descr[i].rxq_stats, 0, + sizeof(s->rxq_descr[i].rxq_stats)); + } + + /* Make sure everything is in place before device activation */ + smp_wmb(); + + vmxnet3_reset_mac(s); + + s->device_active = true; +} + +static void vmxnet3_handle_command(VMXNET3State *s, uint64_t cmd) +{ + s->last_command = cmd; + + switch (cmd) { + case VMXNET3_CMD_GET_PERM_MAC_HI: + VMW_CBPRN("Set: Get upper part of permanent MAC"); + break; + + case VMXNET3_CMD_GET_PERM_MAC_LO: + VMW_CBPRN("Set: Get lower part of permanent MAC"); + break; + + case VMXNET3_CMD_GET_STATS: + VMW_CBPRN("Set: Get device statistics"); + vmxnet3_fill_stats(s); + break; + + case VMXNET3_CMD_ACTIVATE_DEV: + VMW_CBPRN("Set: Activating vmxnet3 device"); + vmxnet3_activate_device(s); + break; + + case VMXNET3_CMD_UPDATE_RX_MODE: + VMW_CBPRN("Set: Update rx mode"); + vmxnet3_update_rx_mode(s); + break; + + case VMXNET3_CMD_UPDATE_VLAN_FILTERS: + VMW_CBPRN("Set: Update VLAN filters"); + vmxnet3_update_vlan_filters(s); + break; + + case VMXNET3_CMD_UPDATE_MAC_FILTERS: + VMW_CBPRN("Set: Update MAC filters"); + vmxnet3_update_mcast_filters(s); + break; + + case VMXNET3_CMD_UPDATE_FEATURE: + VMW_CBPRN("Set: Update features"); + vmxnet3_update_features(s); + break; + + case VMXNET3_CMD_UPDATE_PMCFG: + VMW_CBPRN("Set: Update power management config"); + vmxnet3_update_pm_state(s); + break; + + case VMXNET3_CMD_GET_LINK: + VMW_CBPRN("Set: Get link"); + break; + + case VMXNET3_CMD_RESET_DEV: + VMW_CBPRN("Set: Reset device"); + vmxnet3_reset(s); + break; + + case VMXNET3_CMD_QUIESCE_DEV: + VMW_CBPRN("Set: VMXNET3_CMD_QUIESCE_DEV - pause the device"); + vmxnet3_deactivate_device(s); + break; + + case VMXNET3_CMD_GET_CONF_INTR: + VMW_CBPRN("Set: VMXNET3_CMD_GET_CONF_INTR - interrupt configuration"); + break; + + default: + VMW_CBPRN("Received unknown command: %" PRIx64, cmd); + break; + } +} + +static uint64_t vmxnet3_get_command_status(VMXNET3State *s) +{ + uint64_t ret; + + switch (s->last_command) { + case VMXNET3_CMD_ACTIVATE_DEV: + ret = (s->device_active) ? 0 : -1; + VMW_CFPRN("Device active: %" PRIx64, ret); + break; + + case VMXNET3_CMD_GET_LINK: + ret = s->link_status_and_speed; + VMW_CFPRN("Link and speed: %" PRIx64, ret); + break; + + case VMXNET3_CMD_GET_PERM_MAC_LO: + ret = vmxnet3_get_mac_low(&s->perm_mac); + break; + + case VMXNET3_CMD_GET_PERM_MAC_HI: + ret = vmxnet3_get_mac_high(&s->perm_mac); + break; + + case VMXNET3_CMD_GET_CONF_INTR: + ret = vmxnet3_get_interrupt_config(s); + break; + + default: + VMW_WRPRN("Received request for unknown command: %x", s->last_command); + ret = -1; + break; + } + + return ret; +} + +static void vmxnet3_set_events(VMXNET3State *s, uint32_t val) +{ + uint32_t events; + + VMW_CBPRN("Setting events: 0x%x", val); + events = VMXNET3_READ_DRV_SHARED32(s->drv_shmem, ecr) | val; + VMXNET3_WRITE_DRV_SHARED32(s->drv_shmem, ecr, events); +} + +static void vmxnet3_ack_events(VMXNET3State *s, uint32_t val) +{ + uint32_t events; + + VMW_CBPRN("Clearing events: 0x%x", val); + events = VMXNET3_READ_DRV_SHARED32(s->drv_shmem, ecr) & ~val; + VMXNET3_WRITE_DRV_SHARED32(s->drv_shmem, ecr, events); +} + +static void +vmxnet3_io_bar1_write(void *opaque, + hwaddr addr, + uint64_t val, + unsigned size) +{ + VMXNET3State *s = opaque; + + switch (addr) { + /* Vmxnet3 Revision Report Selection */ + case VMXNET3_REG_VRRS: + VMW_CBPRN("Write BAR1 [VMXNET3_REG_VRRS] = %" PRIx64 ", size %d", + val, size); + break; + + /* UPT Version Report Selection */ + case VMXNET3_REG_UVRS: + VMW_CBPRN("Write BAR1 [VMXNET3_REG_UVRS] = %" PRIx64 ", size %d", + val, size); + break; + + /* Driver Shared Address Low */ + case VMXNET3_REG_DSAL: + VMW_CBPRN("Write BAR1 [VMXNET3_REG_DSAL] = %" PRIx64 ", size %d", + val, size); + /* + * Guest driver will first write the low part of the shared + * memory address. We save it to temp variable and set the + * shared address only after we get the high part + */ + if (0 == val) { + s->device_active = false; + } + s->temp_shared_guest_driver_memory = val; + s->drv_shmem = 0; + break; + + /* Driver Shared Address High */ + case VMXNET3_REG_DSAH: + VMW_CBPRN("Write BAR1 [VMXNET3_REG_DSAH] = %" PRIx64 ", size %d", + val, size); + /* + * Set the shared memory between guest driver and device. + * We already should have low address part. + */ + s->drv_shmem = s->temp_shared_guest_driver_memory | (val << 32); + break; + + /* Command */ + case VMXNET3_REG_CMD: + VMW_CBPRN("Write BAR1 [VMXNET3_REG_CMD] = %" PRIx64 ", size %d", + val, size); + vmxnet3_handle_command(s, val); + break; + + /* MAC Address Low */ + case VMXNET3_REG_MACL: + VMW_CBPRN("Write BAR1 [VMXNET3_REG_MACL] = %" PRIx64 ", size %d", + val, size); + s->temp_mac = val; + break; + + /* MAC Address High */ + case VMXNET3_REG_MACH: + VMW_CBPRN("Write BAR1 [VMXNET3_REG_MACH] = %" PRIx64 ", size %d", + val, size); + vmxnet3_set_variable_mac(s, val, s->temp_mac); + break; + + /* Interrupt Cause Register */ + case VMXNET3_REG_ICR: + VMW_CBPRN("Write BAR1 [VMXNET3_REG_ICR] = %" PRIx64 ", size %d", + val, size); + assert(false); + break; + + /* Event Cause Register */ + case VMXNET3_REG_ECR: + VMW_CBPRN("Write BAR1 [VMXNET3_REG_ECR] = %" PRIx64 ", size %d", + val, size); + vmxnet3_ack_events(s, val); + break; + + default: + VMW_CBPRN("Unknown Write to BAR1 [%" PRIx64 "] = %" PRIx64 ", size %d", + addr, val, size); + break; + } +} + +static uint64_t +vmxnet3_io_bar1_read(void *opaque, hwaddr addr, unsigned size) +{ + VMXNET3State *s = opaque; + uint64_t ret = 0; + + switch (addr) { + /* Vmxnet3 Revision Report Selection */ + case VMXNET3_REG_VRRS: + VMW_CBPRN("Read BAR1 [VMXNET3_REG_VRRS], size %d", size); + ret = VMXNET3_DEVICE_REVISION; + break; + + /* UPT Version Report Selection */ + case VMXNET3_REG_UVRS: + VMW_CBPRN("Read BAR1 [VMXNET3_REG_UVRS], size %d", size); + ret = VMXNET3_DEVICE_VERSION; + break; + + /* Command */ + case VMXNET3_REG_CMD: + VMW_CBPRN("Read BAR1 [VMXNET3_REG_CMD], size %d", size); + ret = vmxnet3_get_command_status(s); + break; + + /* MAC Address Low */ + case VMXNET3_REG_MACL: + VMW_CBPRN("Read BAR1 [VMXNET3_REG_MACL], size %d", size); + ret = vmxnet3_get_mac_low(&s->conf.macaddr); + break; + + /* MAC Address High */ + case VMXNET3_REG_MACH: + VMW_CBPRN("Read BAR1 [VMXNET3_REG_MACH], size %d", size); + ret = vmxnet3_get_mac_high(&s->conf.macaddr); + break; + + /* + * Interrupt Cause Register + * Used for legacy interrupts only so interrupt index always 0 + */ + case VMXNET3_REG_ICR: + VMW_CBPRN("Read BAR1 [VMXNET3_REG_ICR], size %d", size); + if (vmxnet3_interrupt_asserted(s, 0)) { + vmxnet3_clear_interrupt(s, 0); + ret = true; + } else { + ret = false; + } + break; + + default: + VMW_CBPRN("Unknow read BAR1[%" PRIx64 "], %d bytes", addr, size); + break; + } + + return ret; +} + +static int +vmxnet3_can_receive(NetClientState *nc) +{ + VMXNET3State *s = qemu_get_nic_opaque(nc); + return s->device_active && + VMXNET_FLAG_IS_SET(s->link_status_and_speed, VMXNET3_LINK_STATUS_UP); +} + +static inline bool +vmxnet3_is_registered_vlan(VMXNET3State *s, const void *data) +{ + uint16_t vlan_tag = eth_get_pkt_tci(data) & VLAN_VID_MASK; + if (IS_SPECIAL_VLAN_ID(vlan_tag)) { + return true; + } + + return VMXNET3_VFTABLE_ENTRY_IS_SET(s->vlan_table, vlan_tag); +} + +static bool +vmxnet3_is_allowed_mcast_group(VMXNET3State *s, const uint8_t *group_mac) +{ + int i; + for (i = 0; i < s->mcast_list_len; i++) { + if (!memcmp(group_mac, s->mcast_list[i].a, sizeof(s->mcast_list[i]))) { + return true; + } + } + return false; +} + +static bool +vmxnet3_rx_filter_may_indicate(VMXNET3State *s, const void *data, + size_t size) +{ + struct eth_header *ehdr = PKT_GET_ETH_HDR(data); + + if (VMXNET_FLAG_IS_SET(s->rx_mode, VMXNET3_RXM_PROMISC)) { + return true; + } + + if (!vmxnet3_is_registered_vlan(s, data)) { + return false; + } + + switch (vmxnet_rx_pkt_get_packet_type(s->rx_pkt)) { + case ETH_PKT_UCAST: + if (!VMXNET_FLAG_IS_SET(s->rx_mode, VMXNET3_RXM_UCAST)) { + return false; + } + if (memcmp(s->conf.macaddr.a, ehdr->h_dest, ETH_ALEN)) { + return false; + } + break; + + case ETH_PKT_BCAST: + if (!VMXNET_FLAG_IS_SET(s->rx_mode, VMXNET3_RXM_BCAST)) { + return false; + } + break; + + case ETH_PKT_MCAST: + if (VMXNET_FLAG_IS_SET(s->rx_mode, VMXNET3_RXM_ALL_MULTI)) { + return true; + } + if (!VMXNET_FLAG_IS_SET(s->rx_mode, VMXNET3_RXM_MCAST)) { + return false; + } + if (!vmxnet3_is_allowed_mcast_group(s, ehdr->h_dest)) { + return false; + } + break; + + default: + assert(false); + } + + return true; +} + +static ssize_t +vmxnet3_receive(NetClientState *nc, const uint8_t *buf, size_t size) +{ + VMXNET3State *s = qemu_get_nic_opaque(nc); + size_t bytes_indicated; + + if (!vmxnet3_can_receive(nc)) { + VMW_PKPRN("Cannot receive now"); + return -1; + } + + if (s->peer_has_vhdr) { + vmxnet_rx_pkt_set_vhdr(s->rx_pkt, (struct virtio_net_hdr *)buf); + buf += sizeof(struct virtio_net_hdr); + size -= sizeof(struct virtio_net_hdr); + } + + vmxnet_rx_pkt_set_packet_type(s->rx_pkt, + get_eth_packet_type(PKT_GET_ETH_HDR(buf))); + + if (vmxnet3_rx_filter_may_indicate(s, buf, size)) { + vmxnet_rx_pkt_attach_data(s->rx_pkt, buf, size, s->rx_vlan_stripping); + bytes_indicated = vmxnet3_indicate_packet(s) ? size : -1; + if (bytes_indicated < size) { + VMW_PKPRN("RX: %lu of %lu bytes indicated", bytes_indicated, size); + } + } else { + VMW_PKPRN("Packet dropped by RX filter"); + bytes_indicated = size; + } + + assert(size > 0); + assert(bytes_indicated != 0); + return bytes_indicated; +} + +static void vmxnet3_cleanup(NetClientState *nc) +{ + VMXNET3State *s = qemu_get_nic_opaque(nc); + s->nic = NULL; +} + +static void vmxnet3_set_link_status(NetClientState *nc) +{ + VMXNET3State *s = qemu_get_nic_opaque(nc); + + if (nc->link_down) { + s->link_status_and_speed &= ~VMXNET3_LINK_STATUS_UP; + } else { + s->link_status_and_speed |= VMXNET3_LINK_STATUS_UP; + } + + vmxnet3_set_events(s, VMXNET3_ECR_LINK); + vmxnet3_trigger_interrupt(s, s->event_int_idx); +} + +static NetClientInfo net_vmxnet3_info = { + .type = NET_CLIENT_OPTIONS_KIND_NIC, + .size = sizeof(NICState), + .can_receive = vmxnet3_can_receive, + .receive = vmxnet3_receive, + .cleanup = vmxnet3_cleanup, + .link_status_changed = vmxnet3_set_link_status, +}; + +static bool vmxnet3_peer_has_vnet_hdr(VMXNET3State *s) +{ + NetClientState *peer = qemu_get_queue(s->nic)->peer; + + if ((NULL != peer) && + (peer->info->type == NET_CLIENT_OPTIONS_KIND_TAP) && + tap_has_vnet_hdr(peer)) { + return true; + } + + VMW_WRPRN("Peer has no virtio extension. Task offloads will be emulated."); + return false; +} + +static void vmxnet3_net_uninit(VMXNET3State *s) +{ + g_free(s->mcast_list); + vmxnet_tx_pkt_reset(s->tx_pkt); + vmxnet_tx_pkt_uninit(s->tx_pkt); + vmxnet_rx_pkt_uninit(s->rx_pkt); + qemu_del_net_client(qemu_get_queue(s->nic)); +} + +static void vmxnet3_net_init(VMXNET3State *s) +{ + DeviceState *d = DEVICE(s); + + VMW_CBPRN("vmxnet3_net_init called..."); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + + /* Windows guest will query the address that was set on init */ + memcpy(&s->perm_mac.a, &s->conf.macaddr.a, sizeof(s->perm_mac.a)); + + s->mcast_list = NULL; + s->mcast_list_len = 0; + + s->link_status_and_speed = VMXNET3_LINK_SPEED | VMXNET3_LINK_STATUS_UP; + + VMW_CFPRN("Permanent MAC: " MAC_FMT, MAC_ARG(s->perm_mac.a)); + + s->nic = qemu_new_nic(&net_vmxnet3_info, &s->conf, + object_get_typename(OBJECT(s)), + d->id, s); + + s->peer_has_vhdr = vmxnet3_peer_has_vnet_hdr(s); + s->tx_sop = true; + s->skip_current_tx_pkt = false; + s->tx_pkt = NULL; + s->rx_pkt = NULL; + s->rx_vlan_stripping = false; + s->lro_supported = false; + + if (s->peer_has_vhdr) { + tap_set_vnet_hdr_len(qemu_get_queue(s->nic)->peer, + sizeof(struct virtio_net_hdr)); + + tap_using_vnet_hdr(qemu_get_queue(s->nic)->peer, 1); + } + + qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); +} + +static void +vmxnet3_unuse_msix_vectors(VMXNET3State *s, int num_vectors) +{ + PCIDevice *d = PCI_DEVICE(s); + int i; + for (i = 0; i < num_vectors; i++) { + msix_vector_unuse(d, i); + } +} + +static bool +vmxnet3_use_msix_vectors(VMXNET3State *s, int num_vectors) +{ + PCIDevice *d = PCI_DEVICE(s); + int i; + for (i = 0; i < num_vectors; i++) { + int res = msix_vector_use(d, i); + if (0 > res) { + VMW_WRPRN("Failed to use MSI-X vector %d, error %d", i, res); + vmxnet3_unuse_msix_vectors(s, i); + return false; + } + } + return true; +} + +static bool +vmxnet3_init_msix(VMXNET3State *s) +{ + PCIDevice *d = PCI_DEVICE(s); + int res = msix_init(d, VMXNET3_MAX_INTRS, + &s->msix_bar, + VMXNET3_MSIX_BAR_IDX, VMXNET3_OFF_MSIX_TABLE, + &s->msix_bar, + VMXNET3_MSIX_BAR_IDX, VMXNET3_OFF_MSIX_PBA, + 0); + + if (0 > res) { + VMW_WRPRN("Failed to initialize MSI-X, error %d", res); + s->msix_used = false; + } else { + if (!vmxnet3_use_msix_vectors(s, VMXNET3_MAX_INTRS)) { + VMW_WRPRN("Failed to use MSI-X vectors, error %d", res); + msix_uninit(d, &s->msix_bar, &s->msix_bar); + s->msix_used = false; + } else { + s->msix_used = true; + } + } + return s->msix_used; +} + +static void +vmxnet3_cleanup_msix(VMXNET3State *s) +{ + PCIDevice *d = PCI_DEVICE(s); + + if (s->msix_used) { + msix_vector_unuse(d, VMXNET3_MAX_INTRS); + msix_uninit(d, &s->msix_bar, &s->msix_bar); + } +} + +#define VMXNET3_MSI_NUM_VECTORS (1) +#define VMXNET3_MSI_OFFSET (0x50) +#define VMXNET3_USE_64BIT (true) +#define VMXNET3_PER_VECTOR_MASK (false) + +static bool +vmxnet3_init_msi(VMXNET3State *s) +{ + PCIDevice *d = PCI_DEVICE(s); + int res; + + res = msi_init(d, VMXNET3_MSI_OFFSET, VMXNET3_MSI_NUM_VECTORS, + VMXNET3_USE_64BIT, VMXNET3_PER_VECTOR_MASK); + if (0 > res) { + VMW_WRPRN("Failed to initialize MSI, error %d", res); + s->msi_used = false; + } else { + s->msi_used = true; + } + + return s->msi_used; +} + +static void +vmxnet3_cleanup_msi(VMXNET3State *s) +{ + PCIDevice *d = PCI_DEVICE(s); + + if (s->msi_used) { + msi_uninit(d); + } +} + +static void +vmxnet3_msix_save(QEMUFile *f, void *opaque) +{ + PCIDevice *d = PCI_DEVICE(opaque); + msix_save(d, f); +} + +static int +vmxnet3_msix_load(QEMUFile *f, void *opaque, int version_id) +{ + PCIDevice *d = PCI_DEVICE(opaque); + msix_load(d, f); + return 0; +} + +static const MemoryRegionOps b0_ops = { + .read = vmxnet3_io_bar0_read, + .write = vmxnet3_io_bar0_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps b1_ops = { + .read = vmxnet3_io_bar1_read, + .write = vmxnet3_io_bar1_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static int vmxnet3_pci_init(PCIDevice *pci_dev) +{ + DeviceState *dev = DEVICE(pci_dev); + VMXNET3State *s = VMXNET3(pci_dev); + + VMW_CBPRN("Starting init..."); + + memory_region_init_io(&s->bar0, &b0_ops, s, + "vmxnet3-b0", VMXNET3_PT_REG_SIZE); + pci_register_bar(pci_dev, VMXNET3_BAR0_IDX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0); + + memory_region_init_io(&s->bar1, &b1_ops, s, + "vmxnet3-b1", VMXNET3_VD_REG_SIZE); + pci_register_bar(pci_dev, VMXNET3_BAR1_IDX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar1); + + memory_region_init(&s->msix_bar, "vmxnet3-msix-bar", + VMXNET3_MSIX_BAR_SIZE); + pci_register_bar(pci_dev, VMXNET3_MSIX_BAR_IDX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &s->msix_bar); + + vmxnet3_reset_interrupt_states(s); + + /* Interrupt pin A */ + pci_dev->config[PCI_INTERRUPT_PIN] = 0x01; + + if (!vmxnet3_init_msix(s)) { + VMW_WRPRN("Failed to initialize MSI-X, configuration is inconsistent."); + } + + if (!vmxnet3_init_msi(s)) { + VMW_WRPRN("Failed to initialize MSI, configuration is inconsistent."); + } + + vmxnet3_net_init(s); + + register_savevm(dev, "vmxnet3-msix", -1, 1, + vmxnet3_msix_save, vmxnet3_msix_load, s); + + add_boot_device_path(s->conf.bootindex, dev, "/ethernet-phy@0"); + + return 0; +} + + +static void vmxnet3_pci_uninit(PCIDevice *pci_dev) +{ + DeviceState *dev = DEVICE(pci_dev); + VMXNET3State *s = VMXNET3(pci_dev); + + VMW_CBPRN("Starting uninit..."); + + unregister_savevm(dev, "vmxnet3-msix", s); + + vmxnet3_net_uninit(s); + + vmxnet3_cleanup_msix(s); + + vmxnet3_cleanup_msi(s); + + memory_region_destroy(&s->bar0); + memory_region_destroy(&s->bar1); + memory_region_destroy(&s->msix_bar); +} + +static void vmxnet3_qdev_reset(DeviceState *dev) +{ + PCIDevice *d = PCI_DEVICE(dev); + VMXNET3State *s = VMXNET3(d); + + VMW_CBPRN("Starting QDEV reset..."); + vmxnet3_reset(s); +} + +static bool vmxnet3_mc_list_needed(void *opaque) +{ + return true; +} + +static int vmxnet3_mcast_list_pre_load(void *opaque) +{ + VMXNET3State *s = opaque; + + s->mcast_list = g_malloc(s->mcast_list_buff_size); + + return 0; +} + + +static void vmxnet3_pre_save(void *opaque) +{ + VMXNET3State *s = opaque; + + s->mcast_list_buff_size = s->mcast_list_len * sizeof(MACAddr); +} + +static const VMStateDescription vmxstate_vmxnet3_mcast_list = { + .name = "vmxnet3/mcast_list", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .pre_load = vmxnet3_mcast_list_pre_load, + .fields = (VMStateField[]) { + VMSTATE_VBUFFER_UINT32(mcast_list, VMXNET3State, 0, NULL, 0, + mcast_list_buff_size), + VMSTATE_END_OF_LIST() + } +}; + +static void vmxnet3_get_ring_from_file(QEMUFile *f, Vmxnet3Ring *r) +{ + r->pa = qemu_get_be64(f); + r->size = qemu_get_be32(f); + r->cell_size = qemu_get_be32(f); + r->next = qemu_get_be32(f); + r->gen = qemu_get_byte(f); +} + +static void vmxnet3_put_ring_to_file(QEMUFile *f, Vmxnet3Ring *r) +{ + qemu_put_be64(f, r->pa); + qemu_put_be32(f, r->size); + qemu_put_be32(f, r->cell_size); + qemu_put_be32(f, r->next); + qemu_put_byte(f, r->gen); +} + +static void vmxnet3_get_tx_stats_from_file(QEMUFile *f, + struct UPT1_TxStats *tx_stat) +{ + tx_stat->TSOPktsTxOK = qemu_get_be64(f); + tx_stat->TSOBytesTxOK = qemu_get_be64(f); + tx_stat->ucastPktsTxOK = qemu_get_be64(f); + tx_stat->ucastBytesTxOK = qemu_get_be64(f); + tx_stat->mcastPktsTxOK = qemu_get_be64(f); + tx_stat->mcastBytesTxOK = qemu_get_be64(f); + tx_stat->bcastPktsTxOK = qemu_get_be64(f); + tx_stat->bcastBytesTxOK = qemu_get_be64(f); + tx_stat->pktsTxError = qemu_get_be64(f); + tx_stat->pktsTxDiscard = qemu_get_be64(f); +} + +static void vmxnet3_put_tx_stats_to_file(QEMUFile *f, + struct UPT1_TxStats *tx_stat) +{ + qemu_put_be64(f, tx_stat->TSOPktsTxOK); + qemu_put_be64(f, tx_stat->TSOBytesTxOK); + qemu_put_be64(f, tx_stat->ucastPktsTxOK); + qemu_put_be64(f, tx_stat->ucastBytesTxOK); + qemu_put_be64(f, tx_stat->mcastPktsTxOK); + qemu_put_be64(f, tx_stat->mcastBytesTxOK); + qemu_put_be64(f, tx_stat->bcastPktsTxOK); + qemu_put_be64(f, tx_stat->bcastBytesTxOK); + qemu_put_be64(f, tx_stat->pktsTxError); + qemu_put_be64(f, tx_stat->pktsTxDiscard); +} + +static int vmxnet3_get_txq_descr(QEMUFile *f, void *pv, size_t size) +{ + Vmxnet3TxqDescr *r = pv; + + vmxnet3_get_ring_from_file(f, &r->tx_ring); + vmxnet3_get_ring_from_file(f, &r->comp_ring); + r->intr_idx = qemu_get_byte(f); + r->tx_stats_pa = qemu_get_be64(f); + + vmxnet3_get_tx_stats_from_file(f, &r->txq_stats); + + return 0; +} + +static void vmxnet3_put_txq_descr(QEMUFile *f, void *pv, size_t size) +{ + Vmxnet3TxqDescr *r = pv; + + vmxnet3_put_ring_to_file(f, &r->tx_ring); + vmxnet3_put_ring_to_file(f, &r->comp_ring); + qemu_put_byte(f, r->intr_idx); + qemu_put_be64(f, r->tx_stats_pa); + vmxnet3_put_tx_stats_to_file(f, &r->txq_stats); +} + +const VMStateInfo txq_descr_info = { + .name = "txq_descr", + .get = vmxnet3_get_txq_descr, + .put = vmxnet3_put_txq_descr +}; + +static void vmxnet3_get_rx_stats_from_file(QEMUFile *f, + struct UPT1_RxStats *rx_stat) +{ + rx_stat->LROPktsRxOK = qemu_get_be64(f); + rx_stat->LROBytesRxOK = qemu_get_be64(f); + rx_stat->ucastPktsRxOK = qemu_get_be64(f); + rx_stat->ucastBytesRxOK = qemu_get_be64(f); + rx_stat->mcastPktsRxOK = qemu_get_be64(f); + rx_stat->mcastBytesRxOK = qemu_get_be64(f); + rx_stat->bcastPktsRxOK = qemu_get_be64(f); + rx_stat->bcastBytesRxOK = qemu_get_be64(f); + rx_stat->pktsRxOutOfBuf = qemu_get_be64(f); + rx_stat->pktsRxError = qemu_get_be64(f); +} + +static void vmxnet3_put_rx_stats_to_file(QEMUFile *f, + struct UPT1_RxStats *rx_stat) +{ + qemu_put_be64(f, rx_stat->LROPktsRxOK); + qemu_put_be64(f, rx_stat->LROBytesRxOK); + qemu_put_be64(f, rx_stat->ucastPktsRxOK); + qemu_put_be64(f, rx_stat->ucastBytesRxOK); + qemu_put_be64(f, rx_stat->mcastPktsRxOK); + qemu_put_be64(f, rx_stat->mcastBytesRxOK); + qemu_put_be64(f, rx_stat->bcastPktsRxOK); + qemu_put_be64(f, rx_stat->bcastBytesRxOK); + qemu_put_be64(f, rx_stat->pktsRxOutOfBuf); + qemu_put_be64(f, rx_stat->pktsRxError); +} + +static int vmxnet3_get_rxq_descr(QEMUFile *f, void *pv, size_t size) +{ + Vmxnet3RxqDescr *r = pv; + int i; + + for (i = 0; i < VMXNET3_RX_RINGS_PER_QUEUE; i++) { + vmxnet3_get_ring_from_file(f, &r->rx_ring[i]); + } + + vmxnet3_get_ring_from_file(f, &r->comp_ring); + r->intr_idx = qemu_get_byte(f); + r->rx_stats_pa = qemu_get_be64(f); + + vmxnet3_get_rx_stats_from_file(f, &r->rxq_stats); + + return 0; +} + +static void vmxnet3_put_rxq_descr(QEMUFile *f, void *pv, size_t size) +{ + Vmxnet3RxqDescr *r = pv; + int i; + + for (i = 0; i < VMXNET3_RX_RINGS_PER_QUEUE; i++) { + vmxnet3_put_ring_to_file(f, &r->rx_ring[i]); + } + + vmxnet3_put_ring_to_file(f, &r->comp_ring); + qemu_put_byte(f, r->intr_idx); + qemu_put_be64(f, r->rx_stats_pa); + vmxnet3_put_rx_stats_to_file(f, &r->rxq_stats); +} + +static int vmxnet3_post_load(void *opaque, int version_id) +{ + VMXNET3State *s = opaque; + PCIDevice *d = PCI_DEVICE(s); + + vmxnet_tx_pkt_init(&s->tx_pkt, s->max_tx_frags, s->peer_has_vhdr); + vmxnet_rx_pkt_init(&s->rx_pkt, s->peer_has_vhdr); + + if (s->msix_used) { + if (!vmxnet3_use_msix_vectors(s, VMXNET3_MAX_INTRS)) { + VMW_WRPRN("Failed to re-use MSI-X vectors"); + msix_uninit(d, &s->msix_bar, &s->msix_bar); + s->msix_used = false; + return -1; + } + } + + return 0; +} + +const VMStateInfo rxq_descr_info = { + .name = "rxq_descr", + .get = vmxnet3_get_rxq_descr, + .put = vmxnet3_put_rxq_descr +}; + +static int vmxnet3_get_int_state(QEMUFile *f, void *pv, size_t size) +{ + Vmxnet3IntState *r = pv; + + r->is_masked = qemu_get_byte(f); + r->is_pending = qemu_get_byte(f); + r->is_asserted = qemu_get_byte(f); + + return 0; +} + +static void vmxnet3_put_int_state(QEMUFile *f, void *pv, size_t size) +{ + Vmxnet3IntState *r = pv; + + qemu_put_byte(f, r->is_masked); + qemu_put_byte(f, r->is_pending); + qemu_put_byte(f, r->is_asserted); +} + +const VMStateInfo int_state_info = { + .name = "int_state", + .get = vmxnet3_get_int_state, + .put = vmxnet3_put_int_state +}; + +static const VMStateDescription vmstate_vmxnet3 = { + .name = "vmxnet3", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .pre_save = vmxnet3_pre_save, + .post_load = vmxnet3_post_load, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(parent_obj, VMXNET3State), + VMSTATE_BOOL(rx_packets_compound, VMXNET3State), + VMSTATE_BOOL(rx_vlan_stripping, VMXNET3State), + VMSTATE_BOOL(lro_supported, VMXNET3State), + VMSTATE_UINT32(rx_mode, VMXNET3State), + VMSTATE_UINT32(mcast_list_len, VMXNET3State), + VMSTATE_UINT32(mcast_list_buff_size, VMXNET3State), + VMSTATE_UINT32_ARRAY(vlan_table, VMXNET3State, VMXNET3_VFT_SIZE), + VMSTATE_UINT32(mtu, VMXNET3State), + VMSTATE_UINT16(max_rx_frags, VMXNET3State), + VMSTATE_UINT32(max_tx_frags, VMXNET3State), + VMSTATE_UINT8(event_int_idx, VMXNET3State), + VMSTATE_BOOL(auto_int_masking, VMXNET3State), + VMSTATE_UINT8(txq_num, VMXNET3State), + VMSTATE_UINT8(rxq_num, VMXNET3State), + VMSTATE_UINT32(device_active, VMXNET3State), + VMSTATE_UINT32(last_command, VMXNET3State), + VMSTATE_UINT32(link_status_and_speed, VMXNET3State), + VMSTATE_UINT32(temp_mac, VMXNET3State), + VMSTATE_UINT64(drv_shmem, VMXNET3State), + VMSTATE_UINT64(temp_shared_guest_driver_memory, VMXNET3State), + + VMSTATE_ARRAY(txq_descr, VMXNET3State, + VMXNET3_DEVICE_MAX_TX_QUEUES, 0, txq_descr_info, + Vmxnet3TxqDescr), + VMSTATE_ARRAY(rxq_descr, VMXNET3State, + VMXNET3_DEVICE_MAX_RX_QUEUES, 0, rxq_descr_info, + Vmxnet3RxqDescr), + VMSTATE_ARRAY(interrupt_states, VMXNET3State, VMXNET3_MAX_INTRS, + 0, int_state_info, Vmxnet3IntState), + + VMSTATE_END_OF_LIST() + }, + .subsections = (VMStateSubsection[]) { + { + .vmsd = &vmxstate_vmxnet3_mcast_list, + .needed = vmxnet3_mc_list_needed + }, + { + /* empty element. */ + } + } +}; + +static void +vmxnet3_write_config(PCIDevice *pci_dev, uint32_t addr, uint32_t val, int len) +{ + pci_default_write_config(pci_dev, addr, val, len); + msix_write_config(pci_dev, addr, val, len); + msi_write_config(pci_dev, addr, val, len); +} + +static Property vmxnet3_properties[] = { + DEFINE_NIC_PROPERTIES(VMXNET3State, conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static void vmxnet3_class_init(ObjectClass *class, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(class); + PCIDeviceClass *c = PCI_DEVICE_CLASS(class); + + c->init = vmxnet3_pci_init; + c->exit = vmxnet3_pci_uninit; + c->vendor_id = PCI_VENDOR_ID_VMWARE; + c->device_id = PCI_DEVICE_ID_VMWARE_VMXNET3; + c->revision = PCI_DEVICE_ID_VMWARE_VMXNET3_REVISION; + c->class_id = PCI_CLASS_NETWORK_ETHERNET; + c->subsystem_vendor_id = PCI_VENDOR_ID_VMWARE; + c->subsystem_id = PCI_DEVICE_ID_VMWARE_VMXNET3; + c->config_write = vmxnet3_write_config, + dc->desc = "VMWare Paravirtualized Ethernet v3"; + dc->reset = vmxnet3_qdev_reset; + dc->vmsd = &vmstate_vmxnet3; + dc->props = vmxnet3_properties; +} + +static const TypeInfo vmxnet3_info = { + .name = TYPE_VMXNET3, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(VMXNET3State), + .class_init = vmxnet3_class_init, +}; + +static void vmxnet3_register_types(void) +{ + VMW_CBPRN("vmxnet3_register_types called..."); + type_register_static(&vmxnet3_info); +} + +type_init(vmxnet3_register_types) diff --git a/hw/vmxnet3.h b/hw/vmxnet3.h new file mode 100644 index 0000000000..7db0c8f5e0 --- /dev/null +++ b/hw/vmxnet3.h @@ -0,0 +1,760 @@ +/* + * QEMU VMWARE VMXNET3 paravirtual NIC interface definitions + * + * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com) + * + * Developed by Daynix Computing LTD (http://www.daynix.com) + * + * Authors: + * Dmitry Fleytman + * Tamir Shomer + * Yan Vugenfirer + * + * This work is licensed under the terms of the GNU GPL, version 2. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef _QEMU_VMXNET3_H +#define _QEMU_VMXNET3_H + +#define VMXNET3_DEVICE_MAX_TX_QUEUES 8 +#define VMXNET3_DEVICE_MAX_RX_QUEUES 8 /* Keep this value as a power of 2 */ + +/* + * VMWARE headers we got from Linux kernel do not fully comply QEMU coding + * standards in sense of types and defines used. + * Since we didn't want to change VMWARE code, following set of typedefs + * and defines needed to compile these headers with QEMU introduced. + */ +#define u64 uint64_t +#define u32 uint32_t +#define u16 uint16_t +#define u8 uint8_t +#define __le16 uint16_t +#define __le32 uint32_t +#define __le64 uint64_t +#define __packed QEMU_PACKED + +#if defined(HOST_WORDS_BIGENDIAN) +#define const_cpu_to_le64(x) bswap_64(x) +#define __BIG_ENDIAN_BITFIELD +#else +#define const_cpu_to_le64(x) (x) +#endif + +/* + * Following is an interface definition for + * VMXNET3 device as provided by VMWARE + * See original copyright from Linux kernel v3.2.8 + * header file drivers/net/vmxnet3/vmxnet3_defs.h below. + */ + +/* + * Linux driver for VMware's vmxnet3 ethernet NIC. + * + * Copyright (C) 2008-2009, VMware, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; version 2 of the License and no later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Maintained by: Shreyas Bhatewara + * + */ + +struct UPT1_TxStats { + u64 TSOPktsTxOK; /* TSO pkts post-segmentation */ + u64 TSOBytesTxOK; + u64 ucastPktsTxOK; + u64 ucastBytesTxOK; + u64 mcastPktsTxOK; + u64 mcastBytesTxOK; + u64 bcastPktsTxOK; + u64 bcastBytesTxOK; + u64 pktsTxError; + u64 pktsTxDiscard; +}; + +struct UPT1_RxStats { + u64 LROPktsRxOK; /* LRO pkts */ + u64 LROBytesRxOK; /* bytes from LRO pkts */ + /* the following counters are for pkts from the wire, i.e., pre-LRO */ + u64 ucastPktsRxOK; + u64 ucastBytesRxOK; + u64 mcastPktsRxOK; + u64 mcastBytesRxOK; + u64 bcastPktsRxOK; + u64 bcastBytesRxOK; + u64 pktsRxOutOfBuf; + u64 pktsRxError; +}; + +/* interrupt moderation level */ +enum { + UPT1_IML_NONE = 0, /* no interrupt moderation */ + UPT1_IML_HIGHEST = 7, /* least intr generated */ + UPT1_IML_ADAPTIVE = 8, /* adpative intr moderation */ +}; +/* values for UPT1_RSSConf.hashFunc */ +enum { + UPT1_RSS_HASH_TYPE_NONE = 0x0, + UPT1_RSS_HASH_TYPE_IPV4 = 0x01, + UPT1_RSS_HASH_TYPE_TCP_IPV4 = 0x02, + UPT1_RSS_HASH_TYPE_IPV6 = 0x04, + UPT1_RSS_HASH_TYPE_TCP_IPV6 = 0x08, +}; + +enum { + UPT1_RSS_HASH_FUNC_NONE = 0x0, + UPT1_RSS_HASH_FUNC_TOEPLITZ = 0x01, +}; + +#define UPT1_RSS_MAX_KEY_SIZE 40 +#define UPT1_RSS_MAX_IND_TABLE_SIZE 128 + +struct UPT1_RSSConf { + u16 hashType; + u16 hashFunc; + u16 hashKeySize; + u16 indTableSize; + u8 hashKey[UPT1_RSS_MAX_KEY_SIZE]; + u8 indTable[UPT1_RSS_MAX_IND_TABLE_SIZE]; +}; + +/* features */ +enum { + UPT1_F_RXCSUM = const_cpu_to_le64(0x0001), /* rx csum verification */ + UPT1_F_RSS = const_cpu_to_le64(0x0002), + UPT1_F_RXVLAN = const_cpu_to_le64(0x0004), /* VLAN tag stripping */ + UPT1_F_LRO = const_cpu_to_le64(0x0008), +}; + +/* all registers are 32 bit wide */ +/* BAR 1 */ +enum { + VMXNET3_REG_VRRS = 0x0, /* Vmxnet3 Revision Report Selection */ + VMXNET3_REG_UVRS = 0x8, /* UPT Version Report Selection */ + VMXNET3_REG_DSAL = 0x10, /* Driver Shared Address Low */ + VMXNET3_REG_DSAH = 0x18, /* Driver Shared Address High */ + VMXNET3_REG_CMD = 0x20, /* Command */ + VMXNET3_REG_MACL = 0x28, /* MAC Address Low */ + VMXNET3_REG_MACH = 0x30, /* MAC Address High */ + VMXNET3_REG_ICR = 0x38, /* Interrupt Cause Register */ + VMXNET3_REG_ECR = 0x40 /* Event Cause Register */ +}; + +/* BAR 0 */ +enum { + VMXNET3_REG_IMR = 0x0, /* Interrupt Mask Register */ + VMXNET3_REG_TXPROD = 0x600, /* Tx Producer Index */ + VMXNET3_REG_RXPROD = 0x800, /* Rx Producer Index for ring 1 */ + VMXNET3_REG_RXPROD2 = 0xA00 /* Rx Producer Index for ring 2 */ +}; + +#define VMXNET3_PT_REG_SIZE 4096 /* BAR 0 */ +#define VMXNET3_VD_REG_SIZE 4096 /* BAR 1 */ + +#define VMXNET3_REG_ALIGN 8 /* All registers are 8-byte aligned. */ +#define VMXNET3_REG_ALIGN_MASK 0x7 + +/* I/O Mapped access to registers */ +#define VMXNET3_IO_TYPE_PT 0 +#define VMXNET3_IO_TYPE_VD 1 +#define VMXNET3_IO_ADDR(type, reg) (((type) << 24) | ((reg) & 0xFFFFFF)) +#define VMXNET3_IO_TYPE(addr) ((addr) >> 24) +#define VMXNET3_IO_REG(addr) ((addr) & 0xFFFFFF) + +enum { + VMXNET3_CMD_FIRST_SET = 0xCAFE0000, + VMXNET3_CMD_ACTIVATE_DEV = VMXNET3_CMD_FIRST_SET, /* 0xCAFE0000 */ + VMXNET3_CMD_QUIESCE_DEV, /* 0xCAFE0001 */ + VMXNET3_CMD_RESET_DEV, /* 0xCAFE0002 */ + VMXNET3_CMD_UPDATE_RX_MODE, /* 0xCAFE0003 */ + VMXNET3_CMD_UPDATE_MAC_FILTERS, /* 0xCAFE0004 */ + VMXNET3_CMD_UPDATE_VLAN_FILTERS, /* 0xCAFE0005 */ + VMXNET3_CMD_UPDATE_RSSIDT, /* 0xCAFE0006 */ + VMXNET3_CMD_UPDATE_IML, /* 0xCAFE0007 */ + VMXNET3_CMD_UPDATE_PMCFG, /* 0xCAFE0008 */ + VMXNET3_CMD_UPDATE_FEATURE, /* 0xCAFE0009 */ + VMXNET3_CMD_LOAD_PLUGIN, /* 0xCAFE000A */ + + VMXNET3_CMD_FIRST_GET = 0xF00D0000, + VMXNET3_CMD_GET_QUEUE_STATUS = VMXNET3_CMD_FIRST_GET, /* 0xF00D0000 */ + VMXNET3_CMD_GET_STATS, /* 0xF00D0001 */ + VMXNET3_CMD_GET_LINK, /* 0xF00D0002 */ + VMXNET3_CMD_GET_PERM_MAC_LO, /* 0xF00D0003 */ + VMXNET3_CMD_GET_PERM_MAC_HI, /* 0xF00D0004 */ + VMXNET3_CMD_GET_DID_LO, /* 0xF00D0005 */ + VMXNET3_CMD_GET_DID_HI, /* 0xF00D0006 */ + VMXNET3_CMD_GET_DEV_EXTRA_INFO, /* 0xF00D0007 */ + VMXNET3_CMD_GET_CONF_INTR /* 0xF00D0008 */ +}; + +/* + * Little Endian layout of bitfields - + * Byte 0 : 7.....len.....0 + * Byte 1 : rsvd gen 13.len.8 + * Byte 2 : 5.msscof.0 ext1 dtype + * Byte 3 : 13...msscof...6 + * + * Big Endian layout of bitfields - + * Byte 0: 13...msscof...6 + * Byte 1 : 5.msscof.0 ext1 dtype + * Byte 2 : rsvd gen 13.len.8 + * Byte 3 : 7.....len.....0 + * + * Thus, le32_to_cpu on the dword will allow the big endian driver to read + * the bit fields correctly. And cpu_to_le32 will convert bitfields + * bit fields written by big endian driver to format required by device. + */ + +struct Vmxnet3_TxDesc { + __le64 addr; + +#ifdef __BIG_ENDIAN_BITFIELD + u32 msscof:14; /* MSS, checksum offset, flags */ + u32 ext1:1; + u32 dtype:1; /* descriptor type */ + u32 rsvd:1; + u32 gen:1; /* generation bit */ + u32 len:14; +#else + u32 len:14; + u32 gen:1; /* generation bit */ + u32 rsvd:1; + u32 dtype:1; /* descriptor type */ + u32 ext1:1; + u32 msscof:14; /* MSS, checksum offset, flags */ +#endif /* __BIG_ENDIAN_BITFIELD */ + +#ifdef __BIG_ENDIAN_BITFIELD + u32 tci:16; /* Tag to Insert */ + u32 ti:1; /* VLAN Tag Insertion */ + u32 ext2:1; + u32 cq:1; /* completion request */ + u32 eop:1; /* End Of Packet */ + u32 om:2; /* offload mode */ + u32 hlen:10; /* header len */ +#else + u32 hlen:10; /* header len */ + u32 om:2; /* offload mode */ + u32 eop:1; /* End Of Packet */ + u32 cq:1; /* completion request */ + u32 ext2:1; + u32 ti:1; /* VLAN Tag Insertion */ + u32 tci:16; /* Tag to Insert */ +#endif /* __BIG_ENDIAN_BITFIELD */ +}; + +/* TxDesc.OM values */ +#define VMXNET3_OM_NONE 0 +#define VMXNET3_OM_CSUM 2 +#define VMXNET3_OM_TSO 3 + +/* fields in TxDesc we access w/o using bit fields */ +#define VMXNET3_TXD_EOP_SHIFT 12 +#define VMXNET3_TXD_CQ_SHIFT 13 +#define VMXNET3_TXD_GEN_SHIFT 14 +#define VMXNET3_TXD_EOP_DWORD_SHIFT 3 +#define VMXNET3_TXD_GEN_DWORD_SHIFT 2 + +#define VMXNET3_TXD_CQ (1 << VMXNET3_TXD_CQ_SHIFT) +#define VMXNET3_TXD_EOP (1 << VMXNET3_TXD_EOP_SHIFT) +#define VMXNET3_TXD_GEN (1 << VMXNET3_TXD_GEN_SHIFT) + +#define VMXNET3_HDR_COPY_SIZE 128 + + +struct Vmxnet3_TxDataDesc { + u8 data[VMXNET3_HDR_COPY_SIZE]; +}; + +#define VMXNET3_TCD_GEN_SHIFT 31 +#define VMXNET3_TCD_GEN_SIZE 1 +#define VMXNET3_TCD_TXIDX_SHIFT 0 +#define VMXNET3_TCD_TXIDX_SIZE 12 +#define VMXNET3_TCD_GEN_DWORD_SHIFT 3 + +struct Vmxnet3_TxCompDesc { + u32 txdIdx:12; /* Index of the EOP TxDesc */ + u32 ext1:20; + + __le32 ext2; + __le32 ext3; + + u32 rsvd:24; + u32 type:7; /* completion type */ + u32 gen:1; /* generation bit */ +}; + +struct Vmxnet3_RxDesc { + __le64 addr; + +#ifdef __BIG_ENDIAN_BITFIELD + u32 gen:1; /* Generation bit */ + u32 rsvd:15; + u32 dtype:1; /* Descriptor type */ + u32 btype:1; /* Buffer Type */ + u32 len:14; +#else + u32 len:14; + u32 btype:1; /* Buffer Type */ + u32 dtype:1; /* Descriptor type */ + u32 rsvd:15; + u32 gen:1; /* Generation bit */ +#endif + u32 ext1; +}; + +/* values of RXD.BTYPE */ +#define VMXNET3_RXD_BTYPE_HEAD 0 /* head only */ +#define VMXNET3_RXD_BTYPE_BODY 1 /* body only */ + +/* fields in RxDesc we access w/o using bit fields */ +#define VMXNET3_RXD_BTYPE_SHIFT 14 +#define VMXNET3_RXD_GEN_SHIFT 31 + +struct Vmxnet3_RxCompDesc { +#ifdef __BIG_ENDIAN_BITFIELD + u32 ext2:1; + u32 cnc:1; /* Checksum Not Calculated */ + u32 rssType:4; /* RSS hash type used */ + u32 rqID:10; /* rx queue/ring ID */ + u32 sop:1; /* Start of Packet */ + u32 eop:1; /* End of Packet */ + u32 ext1:2; + u32 rxdIdx:12; /* Index of the RxDesc */ +#else + u32 rxdIdx:12; /* Index of the RxDesc */ + u32 ext1:2; + u32 eop:1; /* End of Packet */ + u32 sop:1; /* Start of Packet */ + u32 rqID:10; /* rx queue/ring ID */ + u32 rssType:4; /* RSS hash type used */ + u32 cnc:1; /* Checksum Not Calculated */ + u32 ext2:1; +#endif /* __BIG_ENDIAN_BITFIELD */ + + __le32 rssHash; /* RSS hash value */ + +#ifdef __BIG_ENDIAN_BITFIELD + u32 tci:16; /* Tag stripped */ + u32 ts:1; /* Tag is stripped */ + u32 err:1; /* Error */ + u32 len:14; /* data length */ +#else + u32 len:14; /* data length */ + u32 err:1; /* Error */ + u32 ts:1; /* Tag is stripped */ + u32 tci:16; /* Tag stripped */ +#endif /* __BIG_ENDIAN_BITFIELD */ + + +#ifdef __BIG_ENDIAN_BITFIELD + u32 gen:1; /* generation bit */ + u32 type:7; /* completion type */ + u32 fcs:1; /* Frame CRC correct */ + u32 frg:1; /* IP Fragment */ + u32 v4:1; /* IPv4 */ + u32 v6:1; /* IPv6 */ + u32 ipc:1; /* IP Checksum Correct */ + u32 tcp:1; /* TCP packet */ + u32 udp:1; /* UDP packet */ + u32 tuc:1; /* TCP/UDP Checksum Correct */ + u32 csum:16; +#else + u32 csum:16; + u32 tuc:1; /* TCP/UDP Checksum Correct */ + u32 udp:1; /* UDP packet */ + u32 tcp:1; /* TCP packet */ + u32 ipc:1; /* IP Checksum Correct */ + u32 v6:1; /* IPv6 */ + u32 v4:1; /* IPv4 */ + u32 frg:1; /* IP Fragment */ + u32 fcs:1; /* Frame CRC correct */ + u32 type:7; /* completion type */ + u32 gen:1; /* generation bit */ +#endif /* __BIG_ENDIAN_BITFIELD */ +}; + +/* fields in RxCompDesc we access via Vmxnet3_GenericDesc.dword[3] */ +#define VMXNET3_RCD_TUC_SHIFT 16 +#define VMXNET3_RCD_IPC_SHIFT 19 + +/* fields in RxCompDesc we access via Vmxnet3_GenericDesc.qword[1] */ +#define VMXNET3_RCD_TYPE_SHIFT 56 +#define VMXNET3_RCD_GEN_SHIFT 63 + +/* csum OK for TCP/UDP pkts over IP */ +#define VMXNET3_RCD_CSUM_OK (1 << VMXNET3_RCD_TUC_SHIFT | \ + 1 << VMXNET3_RCD_IPC_SHIFT) +#define VMXNET3_TXD_GEN_SIZE 1 +#define VMXNET3_TXD_EOP_SIZE 1 + +/* value of RxCompDesc.rssType */ +enum { + VMXNET3_RCD_RSS_TYPE_NONE = 0, + VMXNET3_RCD_RSS_TYPE_IPV4 = 1, + VMXNET3_RCD_RSS_TYPE_TCPIPV4 = 2, + VMXNET3_RCD_RSS_TYPE_IPV6 = 3, + VMXNET3_RCD_RSS_TYPE_TCPIPV6 = 4, +}; + + +/* a union for accessing all cmd/completion descriptors */ +union Vmxnet3_GenericDesc { + __le64 qword[2]; + __le32 dword[4]; + __le16 word[8]; + struct Vmxnet3_TxDesc txd; + struct Vmxnet3_RxDesc rxd; + struct Vmxnet3_TxCompDesc tcd; + struct Vmxnet3_RxCompDesc rcd; +}; + +#define VMXNET3_INIT_GEN 1 + +/* Max size of a single tx buffer */ +#define VMXNET3_MAX_TX_BUF_SIZE (1 << 14) + +/* # of tx desc needed for a tx buffer size */ +#define VMXNET3_TXD_NEEDED(size) (((size) + VMXNET3_MAX_TX_BUF_SIZE - 1) / \ + VMXNET3_MAX_TX_BUF_SIZE) + +/* max # of tx descs for a non-tso pkt */ +#define VMXNET3_MAX_TXD_PER_PKT 16 + +/* Max size of a single rx buffer */ +#define VMXNET3_MAX_RX_BUF_SIZE ((1 << 14) - 1) +/* Minimum size of a type 0 buffer */ +#define VMXNET3_MIN_T0_BUF_SIZE 128 +#define VMXNET3_MAX_CSUM_OFFSET 1024 + +/* Ring base address alignment */ +#define VMXNET3_RING_BA_ALIGN 512 +#define VMXNET3_RING_BA_MASK (VMXNET3_RING_BA_ALIGN - 1) + +/* Ring size must be a multiple of 32 */ +#define VMXNET3_RING_SIZE_ALIGN 32 +#define VMXNET3_RING_SIZE_MASK (VMXNET3_RING_SIZE_ALIGN - 1) + +/* Max ring size */ +#define VMXNET3_TX_RING_MAX_SIZE 4096 +#define VMXNET3_TC_RING_MAX_SIZE 4096 +#define VMXNET3_RX_RING_MAX_SIZE 4096 +#define VMXNET3_RC_RING_MAX_SIZE 8192 + +/* a list of reasons for queue stop */ + +enum { + VMXNET3_ERR_NOEOP = 0x80000000, /* cannot find the EOP desc of a pkt */ + VMXNET3_ERR_TXD_REUSE = 0x80000001, /* reuse TxDesc before tx completion */ + VMXNET3_ERR_BIG_PKT = 0x80000002, /* too many TxDesc for a pkt */ + VMXNET3_ERR_DESC_NOT_SPT = 0x80000003, /* descriptor type not supported */ + VMXNET3_ERR_SMALL_BUF = 0x80000004, /* type 0 buffer too small */ + VMXNET3_ERR_STRESS = 0x80000005, /* stress option firing in vmkernel */ + VMXNET3_ERR_SWITCH = 0x80000006, /* mode switch failure */ + VMXNET3_ERR_TXD_INVALID = 0x80000007, /* invalid TxDesc */ +}; + +/* completion descriptor types */ +#define VMXNET3_CDTYPE_TXCOMP 0 /* Tx Completion Descriptor */ +#define VMXNET3_CDTYPE_RXCOMP 3 /* Rx Completion Descriptor */ + +enum { + VMXNET3_GOS_BITS_UNK = 0, /* unknown */ + VMXNET3_GOS_BITS_32 = 1, + VMXNET3_GOS_BITS_64 = 2, +}; + +#define VMXNET3_GOS_TYPE_UNK 0 /* unknown */ +#define VMXNET3_GOS_TYPE_LINUX 1 +#define VMXNET3_GOS_TYPE_WIN 2 +#define VMXNET3_GOS_TYPE_SOLARIS 3 +#define VMXNET3_GOS_TYPE_FREEBSD 4 +#define VMXNET3_GOS_TYPE_PXE 5 + +struct Vmxnet3_GOSInfo { +#ifdef __BIG_ENDIAN_BITFIELD + u32 gosMisc:10; /* other info about gos */ + u32 gosVer:16; /* gos version */ + u32 gosType:4; /* which guest */ + u32 gosBits:2; /* 32-bit or 64-bit? */ +#else + u32 gosBits:2; /* 32-bit or 64-bit? */ + u32 gosType:4; /* which guest */ + u32 gosVer:16; /* gos version */ + u32 gosMisc:10; /* other info about gos */ +#endif /* __BIG_ENDIAN_BITFIELD */ +}; + +struct Vmxnet3_DriverInfo { + __le32 version; + struct Vmxnet3_GOSInfo gos; + __le32 vmxnet3RevSpt; + __le32 uptVerSpt; +}; + + +#define VMXNET3_REV1_MAGIC 0xbabefee1 + +/* + * QueueDescPA must be 128 bytes aligned. It points to an array of + * Vmxnet3_TxQueueDesc followed by an array of Vmxnet3_RxQueueDesc. + * The number of Vmxnet3_TxQueueDesc/Vmxnet3_RxQueueDesc are specified by + * Vmxnet3_MiscConf.numTxQueues/numRxQueues, respectively. + */ +#define VMXNET3_QUEUE_DESC_ALIGN 128 + + +struct Vmxnet3_MiscConf { + struct Vmxnet3_DriverInfo driverInfo; + __le64 uptFeatures; + __le64 ddPA; /* driver data PA */ + __le64 queueDescPA; /* queue descriptor table PA */ + __le32 ddLen; /* driver data len */ + __le32 queueDescLen; /* queue desc. table len in bytes */ + __le32 mtu; + __le16 maxNumRxSG; + u8 numTxQueues; + u8 numRxQueues; + __le32 reserved[4]; +}; + + +struct Vmxnet3_TxQueueConf { + __le64 txRingBasePA; + __le64 dataRingBasePA; + __le64 compRingBasePA; + __le64 ddPA; /* driver data */ + __le64 reserved; + __le32 txRingSize; /* # of tx desc */ + __le32 dataRingSize; /* # of data desc */ + __le32 compRingSize; /* # of comp desc */ + __le32 ddLen; /* size of driver data */ + u8 intrIdx; + u8 _pad[7]; +}; + + +struct Vmxnet3_RxQueueConf { + __le64 rxRingBasePA[2]; + __le64 compRingBasePA; + __le64 ddPA; /* driver data */ + __le64 reserved; + __le32 rxRingSize[2]; /* # of rx desc */ + __le32 compRingSize; /* # of rx comp desc */ + __le32 ddLen; /* size of driver data */ + u8 intrIdx; + u8 _pad[7]; +}; + + +enum vmxnet3_intr_mask_mode { + VMXNET3_IMM_AUTO = 0, + VMXNET3_IMM_ACTIVE = 1, + VMXNET3_IMM_LAZY = 2 +}; + +enum vmxnet3_intr_type { + VMXNET3_IT_AUTO = 0, + VMXNET3_IT_INTX = 1, + VMXNET3_IT_MSI = 2, + VMXNET3_IT_MSIX = 3 +}; + +#define VMXNET3_MAX_TX_QUEUES 8 +#define VMXNET3_MAX_RX_QUEUES 16 +/* addition 1 for events */ +#define VMXNET3_MAX_INTRS 25 + +/* value of intrCtrl */ +#define VMXNET3_IC_DISABLE_ALL 0x1 /* bit 0 */ + + +struct Vmxnet3_IntrConf { + bool autoMask; + u8 numIntrs; /* # of interrupts */ + u8 eventIntrIdx; + u8 modLevels[VMXNET3_MAX_INTRS]; /* moderation level for + * each intr */ + __le32 intrCtrl; + __le32 reserved[2]; +}; + +/* one bit per VLAN ID, the size is in the units of u32 */ +#define VMXNET3_VFT_SIZE (4096/(sizeof(uint32_t)*8)) + + +struct Vmxnet3_QueueStatus { + bool stopped; + u8 _pad[3]; + __le32 error; +}; + + +struct Vmxnet3_TxQueueCtrl { + __le32 txNumDeferred; + __le32 txThreshold; + __le64 reserved; +}; + + +struct Vmxnet3_RxQueueCtrl { + bool updateRxProd; + u8 _pad[7]; + __le64 reserved; +}; + +enum { + VMXNET3_RXM_UCAST = 0x01, /* unicast only */ + VMXNET3_RXM_MCAST = 0x02, /* multicast passing the filters */ + VMXNET3_RXM_BCAST = 0x04, /* broadcast only */ + VMXNET3_RXM_ALL_MULTI = 0x08, /* all multicast */ + VMXNET3_RXM_PROMISC = 0x10 /* promiscuous */ +}; + +struct Vmxnet3_RxFilterConf { + __le32 rxMode; /* VMXNET3_RXM_xxx */ + __le16 mfTableLen; /* size of the multicast filter table */ + __le16 _pad1; + __le64 mfTablePA; /* PA of the multicast filters table */ + __le32 vfTable[VMXNET3_VFT_SIZE]; /* vlan filter */ +}; + + +#define VMXNET3_PM_MAX_FILTERS 6 +#define VMXNET3_PM_MAX_PATTERN_SIZE 128 +#define VMXNET3_PM_MAX_MASK_SIZE (VMXNET3_PM_MAX_PATTERN_SIZE / 8) + +#define VMXNET3_PM_WAKEUP_MAGIC cpu_to_le16(0x01) /* wake up on magic pkts */ +#define VMXNET3_PM_WAKEUP_FILTER cpu_to_le16(0x02) /* wake up on pkts matching + * filters */ + + +struct Vmxnet3_PM_PktFilter { + u8 maskSize; + u8 patternSize; + u8 mask[VMXNET3_PM_MAX_MASK_SIZE]; + u8 pattern[VMXNET3_PM_MAX_PATTERN_SIZE]; + u8 pad[6]; +}; + + +struct Vmxnet3_PMConf { + __le16 wakeUpEvents; /* VMXNET3_PM_WAKEUP_xxx */ + u8 numFilters; + u8 pad[5]; + struct Vmxnet3_PM_PktFilter filters[VMXNET3_PM_MAX_FILTERS]; +}; + + +struct Vmxnet3_VariableLenConfDesc { + __le32 confVer; + __le32 confLen; + __le64 confPA; +}; + + +struct Vmxnet3_TxQueueDesc { + struct Vmxnet3_TxQueueCtrl ctrl; + struct Vmxnet3_TxQueueConf conf; + + /* Driver read after a GET command */ + struct Vmxnet3_QueueStatus status; + struct UPT1_TxStats stats; + u8 _pad[88]; /* 128 aligned */ +}; + + +struct Vmxnet3_RxQueueDesc { + struct Vmxnet3_RxQueueCtrl ctrl; + struct Vmxnet3_RxQueueConf conf; + /* Driver read after a GET commad */ + struct Vmxnet3_QueueStatus status; + struct UPT1_RxStats stats; + u8 __pad[88]; /* 128 aligned */ +}; + + +struct Vmxnet3_DSDevRead { + /* read-only region for device, read by dev in response to a SET cmd */ + struct Vmxnet3_MiscConf misc; + struct Vmxnet3_IntrConf intrConf; + struct Vmxnet3_RxFilterConf rxFilterConf; + struct Vmxnet3_VariableLenConfDesc rssConfDesc; + struct Vmxnet3_VariableLenConfDesc pmConfDesc; + struct Vmxnet3_VariableLenConfDesc pluginConfDesc; +}; + +/* All structures in DriverShared are padded to multiples of 8 bytes */ +struct Vmxnet3_DriverShared { + __le32 magic; + /* make devRead start at 64bit boundaries */ + __le32 pad; + struct Vmxnet3_DSDevRead devRead; + __le32 ecr; + __le32 reserved[5]; +}; + + +#define VMXNET3_ECR_RQERR (1 << 0) +#define VMXNET3_ECR_TQERR (1 << 1) +#define VMXNET3_ECR_LINK (1 << 2) +#define VMXNET3_ECR_DIC (1 << 3) +#define VMXNET3_ECR_DEBUG (1 << 4) + +/* flip the gen bit of a ring */ +#define VMXNET3_FLIP_RING_GEN(gen) ((gen) = (gen) ^ 0x1) + +/* only use this if moving the idx won't affect the gen bit */ +#define VMXNET3_INC_RING_IDX_ONLY(idx, ring_size) \ + do {\ + (idx)++;\ + if (unlikely((idx) == (ring_size))) {\ + (idx) = 0;\ + } \ + } while (0) + +#define VMXNET3_SET_VFTABLE_ENTRY(vfTable, vid) \ + (vfTable[vid >> 5] |= (1 << (vid & 31))) +#define VMXNET3_CLEAR_VFTABLE_ENTRY(vfTable, vid) \ + (vfTable[vid >> 5] &= ~(1 << (vid & 31))) + +#define VMXNET3_VFTABLE_ENTRY_IS_SET(vfTable, vid) \ + ((vfTable[vid >> 5] & (1 << (vid & 31))) != 0) + +#define VMXNET3_MAX_MTU 9000 +#define VMXNET3_MIN_MTU 60 + +#define VMXNET3_LINK_UP (10000 << 16 | 1) /* 10 Gbps, up */ +#define VMXNET3_LINK_DOWN 0 + +#undef u64 +#undef u32 +#undef u16 +#undef u8 +#undef __le16 +#undef __le32 +#undef __le64 +#undef __packed +#undef const_cpu_to_le64 +#if defined(HOST_WORDS_BIGENDIAN) +#undef __BIG_ENDIAN_BITFIELD +#endif + +#endif diff --git a/hw/vmxnet_debug.h b/hw/vmxnet_debug.h new file mode 100644 index 0000000000..96dae0f916 --- /dev/null +++ b/hw/vmxnet_debug.h @@ -0,0 +1,115 @@ +/* + * QEMU VMWARE VMXNET* paravirtual NICs - debugging facilities + * + * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com) + * + * Developed by Daynix Computing LTD (http://www.daynix.com) + * + * Authors: + * Dmitry Fleytman + * Tamir Shomer + * Yan Vugenfirer + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef _QEMU_VMXNET_DEBUG_H +#define _QEMU_VMXNET_DEBUG_H + +#define VMXNET_DEVICE_NAME "vmxnet3" + +/* #define VMXNET_DEBUG_CB */ +#define VMXNET_DEBUG_WARNINGS +#define VMXNET_DEBUG_ERRORS +/* #define VMXNET_DEBUG_INTERRUPTS */ +/* #define VMXNET_DEBUG_CONFIG */ +/* #define VMXNET_DEBUG_RINGS */ +/* #define VMXNET_DEBUG_PACKETS */ +/* #define VMXNET_DEBUG_SHMEM_ACCESS */ + +#ifdef VMXNET_DEBUG_SHMEM_ACCESS +#define VMW_SHPRN(fmt, ...) \ + do { \ + printf("[%s][SH][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \ + ## __VA_ARGS__); \ + } while (0) +#else +#define VMW_SHPRN(fmt, ...) do {} while (0) +#endif + +#ifdef VMXNET_DEBUG_CB +#define VMW_CBPRN(fmt, ...) \ + do { \ + printf("[%s][CB][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \ + ## __VA_ARGS__); \ + } while (0) +#else +#define VMW_CBPRN(fmt, ...) do {} while (0) +#endif + +#ifdef VMXNET_DEBUG_PACKETS +#define VMW_PKPRN(fmt, ...) \ + do { \ + printf("[%s][PK][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \ + ## __VA_ARGS__); \ + } while (0) +#else +#define VMW_PKPRN(fmt, ...) do {} while (0) +#endif + +#ifdef VMXNET_DEBUG_WARNINGS +#define VMW_WRPRN(fmt, ...) \ + do { \ + printf("[%s][WR][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \ + ## __VA_ARGS__); \ + } while (0) +#else +#define VMW_WRPRN(fmt, ...) do {} while (0) +#endif + +#ifdef VMXNET_DEBUG_ERRORS +#define VMW_ERPRN(fmt, ...) \ + do { \ + printf("[%s][ER][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \ + ## __VA_ARGS__); \ + } while (0) +#else +#define VMW_ERPRN(fmt, ...) do {} while (0) +#endif + +#ifdef VMXNET_DEBUG_INTERRUPTS +#define VMW_IRPRN(fmt, ...) \ + do { \ + printf("[%s][IR][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \ + ## __VA_ARGS__); \ + } while (0) +#else +#define VMW_IRPRN(fmt, ...) do {} while (0) +#endif + +#ifdef VMXNET_DEBUG_CONFIG +#define VMW_CFPRN(fmt, ...) \ + do { \ + printf("[%s][CF][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \ + ## __VA_ARGS__); \ + } while (0) +#else +#define VMW_CFPRN(fmt, ...) do {} while (0) +#endif + +#ifdef VMXNET_DEBUG_RINGS +#define VMW_RIPRN(fmt, ...) \ + do { \ + printf("[%s][RI][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \ + ## __VA_ARGS__); \ + } while (0) +#else +#define VMW_RIPRN(fmt, ...) do {} while (0) +#endif + +#define VMXNET_MF "%02X:%02X:%02X:%02X:%02X:%02X" +#define VMXNET_MA(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5] + +#endif /* _QEMU_VMXNET3_DEBUG_H */ diff --git a/hw/vmxnet_rx_pkt.c b/hw/vmxnet_rx_pkt.c new file mode 100644 index 0000000000..a40e346293 --- /dev/null +++ b/hw/vmxnet_rx_pkt.c @@ -0,0 +1,187 @@ +/* + * QEMU VMWARE VMXNET* paravirtual NICs - RX packets abstractions + * + * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com) + * + * Developed by Daynix Computing LTD (http://www.daynix.com) + * + * Authors: + * Dmitry Fleytman + * Tamir Shomer + * Yan Vugenfirer + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "vmxnet_rx_pkt.h" +#include "net/eth.h" +#include "qemu-common.h" +#include "qemu/iov.h" +#include "net/checksum.h" +#include "net/tap.h" + +/* + * RX packet may contain up to 2 fragments - rebuilt eth header + * in case of VLAN tag stripping + * and payload received from QEMU - in any case + */ +#define VMXNET_MAX_RX_PACKET_FRAGMENTS (2) + +struct VmxnetRxPkt { + struct virtio_net_hdr virt_hdr; + uint8_t ehdr_buf[ETH_MAX_L2_HDR_LEN]; + struct iovec vec[VMXNET_MAX_RX_PACKET_FRAGMENTS]; + uint16_t vec_len; + uint32_t tot_len; + uint16_t tci; + bool vlan_stripped; + bool has_virt_hdr; + eth_pkt_types_e packet_type; + + /* Analysis results */ + bool isip4; + bool isip6; + bool isudp; + bool istcp; +}; + +void vmxnet_rx_pkt_init(struct VmxnetRxPkt **pkt, bool has_virt_hdr) +{ + struct VmxnetRxPkt *p = g_malloc0(sizeof *p); + p->has_virt_hdr = has_virt_hdr; + *pkt = p; +} + +void vmxnet_rx_pkt_uninit(struct VmxnetRxPkt *pkt) +{ + g_free(pkt); +} + +struct virtio_net_hdr *vmxnet_rx_pkt_get_vhdr(struct VmxnetRxPkt *pkt) +{ + assert(pkt); + return &pkt->virt_hdr; +} + +void vmxnet_rx_pkt_attach_data(struct VmxnetRxPkt *pkt, const void *data, + size_t len, bool strip_vlan) +{ + uint16_t tci = 0; + uint16_t ploff; + assert(pkt); + pkt->vlan_stripped = false; + + if (strip_vlan) { + pkt->vlan_stripped = eth_strip_vlan(data, pkt->ehdr_buf, &ploff, &tci); + } + + if (pkt->vlan_stripped) { + pkt->vec[0].iov_base = pkt->ehdr_buf; + pkt->vec[0].iov_len = ploff - sizeof(struct vlan_header); + pkt->vec[1].iov_base = (uint8_t *) data + ploff; + pkt->vec[1].iov_len = len - ploff; + pkt->vec_len = 2; + pkt->tot_len = len - ploff + sizeof(struct eth_header); + } else { + pkt->vec[0].iov_base = (void *)data; + pkt->vec[0].iov_len = len; + pkt->vec_len = 1; + pkt->tot_len = len; + } + + pkt->tci = tci; + + eth_get_protocols(data, len, &pkt->isip4, &pkt->isip6, + &pkt->isudp, &pkt->istcp); +} + +void vmxnet_rx_pkt_dump(struct VmxnetRxPkt *pkt) +{ +#ifdef VMXNET_RX_PKT_DEBUG + VmxnetRxPkt *pkt = (VmxnetRxPkt *)pkt; + assert(pkt); + + printf("RX PKT: tot_len: %d, vlan_stripped: %d, vlan_tag: %d\n", + pkt->tot_len, pkt->vlan_stripped, pkt->tci); +#endif +} + +void vmxnet_rx_pkt_set_packet_type(struct VmxnetRxPkt *pkt, + eth_pkt_types_e packet_type) +{ + assert(pkt); + + pkt->packet_type = packet_type; + +} + +eth_pkt_types_e vmxnet_rx_pkt_get_packet_type(struct VmxnetRxPkt *pkt) +{ + assert(pkt); + + return pkt->packet_type; +} + +size_t vmxnet_rx_pkt_get_total_len(struct VmxnetRxPkt *pkt) +{ + assert(pkt); + + return pkt->tot_len; +} + +void vmxnet_rx_pkt_get_protocols(struct VmxnetRxPkt *pkt, + bool *isip4, bool *isip6, + bool *isudp, bool *istcp) +{ + assert(pkt); + + *isip4 = pkt->isip4; + *isip6 = pkt->isip6; + *isudp = pkt->isudp; + *istcp = pkt->istcp; +} + +struct iovec *vmxnet_rx_pkt_get_iovec(struct VmxnetRxPkt *pkt) +{ + assert(pkt); + + return pkt->vec; +} + +void vmxnet_rx_pkt_set_vhdr(struct VmxnetRxPkt *pkt, + struct virtio_net_hdr *vhdr) +{ + assert(pkt); + + memcpy(&pkt->virt_hdr, vhdr, sizeof pkt->virt_hdr); +} + +bool vmxnet_rx_pkt_is_vlan_stripped(struct VmxnetRxPkt *pkt) +{ + assert(pkt); + + return pkt->vlan_stripped; +} + +bool vmxnet_rx_pkt_has_virt_hdr(struct VmxnetRxPkt *pkt) +{ + assert(pkt); + + return pkt->has_virt_hdr; +} + +uint16_t vmxnet_rx_pkt_get_num_frags(struct VmxnetRxPkt *pkt) +{ + assert(pkt); + + return pkt->vec_len; +} + +uint16_t vmxnet_rx_pkt_get_vlan_tag(struct VmxnetRxPkt *pkt) +{ + assert(pkt); + + return pkt->tci; +} diff --git a/hw/vmxnet_rx_pkt.h b/hw/vmxnet_rx_pkt.h new file mode 100644 index 0000000000..6b2c60ef10 --- /dev/null +++ b/hw/vmxnet_rx_pkt.h @@ -0,0 +1,174 @@ +/* + * QEMU VMWARE VMXNET* paravirtual NICs - RX packets abstraction + * + * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com) + * + * Developed by Daynix Computing LTD (http://www.daynix.com) + * + * Authors: + * Dmitry Fleytman + * Tamir Shomer + * Yan Vugenfirer + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef VMXNET_RX_PKT_H +#define VMXNET_RX_PKT_H + +#include "stdint.h" +#include "stdbool.h" +#include "net/eth.h" + +/* defines to enable packet dump functions */ +/*#define VMXNET_RX_PKT_DEBUG*/ + +struct VmxnetRxPkt; + +/** + * Clean all rx packet resources + * + * @pkt: packet + * + */ +void vmxnet_rx_pkt_uninit(struct VmxnetRxPkt *pkt); + +/** + * Init function for rx packet functionality + * + * @pkt: packet pointer + * @has_virt_hdr: device uses virtio header + * + */ +void vmxnet_rx_pkt_init(struct VmxnetRxPkt **pkt, bool has_virt_hdr); + +/** + * returns total length of data attached to rx context + * + * @pkt: packet + * + * Return: nothing + * + */ +size_t vmxnet_rx_pkt_get_total_len(struct VmxnetRxPkt *pkt); + +/** + * fetches packet analysis results + * + * @pkt: packet + * @isip4: whether the packet given is IPv4 + * @isip6: whether the packet given is IPv6 + * @isudp: whether the packet given is UDP + * @istcp: whether the packet given is TCP + * + */ +void vmxnet_rx_pkt_get_protocols(struct VmxnetRxPkt *pkt, + bool *isip4, bool *isip6, + bool *isudp, bool *istcp); + +/** + * returns virtio header stored in rx context + * + * @pkt: packet + * @ret: virtio header + * + */ +struct virtio_net_hdr *vmxnet_rx_pkt_get_vhdr(struct VmxnetRxPkt *pkt); + +/** + * returns packet type + * + * @pkt: packet + * @ret: packet type + * + */ +eth_pkt_types_e vmxnet_rx_pkt_get_packet_type(struct VmxnetRxPkt *pkt); + +/** + * returns vlan tag + * + * @pkt: packet + * @ret: VLAN tag + * + */ +uint16_t vmxnet_rx_pkt_get_vlan_tag(struct VmxnetRxPkt *pkt); + +/** + * tells whether vlan was stripped from the packet + * + * @pkt: packet + * @ret: VLAN stripped sign + * + */ +bool vmxnet_rx_pkt_is_vlan_stripped(struct VmxnetRxPkt *pkt); + +/** + * notifies caller if the packet has virtio header + * + * @pkt: packet + * @ret: true if packet has virtio header, false otherwize + * + */ +bool vmxnet_rx_pkt_has_virt_hdr(struct VmxnetRxPkt *pkt); + +/** + * returns number of frags attached to the packet + * + * @pkt: packet + * @ret: number of frags + * + */ +uint16_t vmxnet_rx_pkt_get_num_frags(struct VmxnetRxPkt *pkt); + +/** + * attach data to rx packet + * + * @pkt: packet + * @data: pointer to the data buffer + * @len: data length + * @strip_vlan: should the module strip vlan from data + * + */ +void vmxnet_rx_pkt_attach_data(struct VmxnetRxPkt *pkt, const void *data, + size_t len, bool strip_vlan); + +/** + * returns io vector that holds the attached data + * + * @pkt: packet + * @ret: pointer to IOVec + * + */ +struct iovec *vmxnet_rx_pkt_get_iovec(struct VmxnetRxPkt *pkt); + +/** + * prints rx packet data if debug is enabled + * + * @pkt: packet + * + */ +void vmxnet_rx_pkt_dump(struct VmxnetRxPkt *pkt); + +/** + * copy passed vhdr data to packet context + * + * @pkt: packet + * @vhdr: VHDR buffer + * + */ +void vmxnet_rx_pkt_set_vhdr(struct VmxnetRxPkt *pkt, + struct virtio_net_hdr *vhdr); + +/** + * save packet type in packet context + * + * @pkt: packet + * @packet_type: the packet type + * + */ +void vmxnet_rx_pkt_set_packet_type(struct VmxnetRxPkt *pkt, + eth_pkt_types_e packet_type); + +#endif diff --git a/hw/vmxnet_tx_pkt.c b/hw/vmxnet_tx_pkt.c new file mode 100644 index 0000000000..b1e795b3b2 --- /dev/null +++ b/hw/vmxnet_tx_pkt.c @@ -0,0 +1,567 @@ +/* + * QEMU VMWARE VMXNET* paravirtual NICs - TX packets abstractions + * + * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com) + * + * Developed by Daynix Computing LTD (http://www.daynix.com) + * + * Authors: + * Dmitry Fleytman + * Tamir Shomer + * Yan Vugenfirer + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "vmxnet_tx_pkt.h" +#include "net/eth.h" +#include "qemu-common.h" +#include "qemu/iov.h" +#include "net/checksum.h" +#include "net/tap.h" +#include "net/net.h" +#include "exec/cpu-common.h" + +enum { + VMXNET_TX_PKT_VHDR_FRAG = 0, + VMXNET_TX_PKT_L2HDR_FRAG, + VMXNET_TX_PKT_L3HDR_FRAG, + VMXNET_TX_PKT_PL_START_FRAG +}; + +/* TX packet private context */ +struct VmxnetTxPkt { + struct virtio_net_hdr virt_hdr; + bool has_virt_hdr; + + struct iovec *raw; + uint32_t raw_frags; + uint32_t max_raw_frags; + + struct iovec *vec; + + uint8_t l2_hdr[ETH_MAX_L2_HDR_LEN]; + + uint32_t payload_len; + + uint32_t payload_frags; + uint32_t max_payload_frags; + + uint16_t hdr_len; + eth_pkt_types_e packet_type; + uint8_t l4proto; +}; + +void vmxnet_tx_pkt_init(struct VmxnetTxPkt **pkt, uint32_t max_frags, + bool has_virt_hdr) +{ + struct VmxnetTxPkt *p = g_malloc0(sizeof *p); + + p->vec = g_malloc((sizeof *p->vec) * + (max_frags + VMXNET_TX_PKT_PL_START_FRAG)); + + p->raw = g_malloc((sizeof *p->raw) * max_frags); + + p->max_payload_frags = max_frags; + p->max_raw_frags = max_frags; + p->has_virt_hdr = has_virt_hdr; + p->vec[VMXNET_TX_PKT_VHDR_FRAG].iov_base = &p->virt_hdr; + p->vec[VMXNET_TX_PKT_VHDR_FRAG].iov_len = + p->has_virt_hdr ? sizeof p->virt_hdr : 0; + p->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_base = &p->l2_hdr; + p->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base = NULL; + p->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len = 0; + + *pkt = p; +} + +void vmxnet_tx_pkt_uninit(struct VmxnetTxPkt *pkt) +{ + if (pkt) { + g_free(pkt->vec); + g_free(pkt->raw); + g_free(pkt); + } +} + +void vmxnet_tx_pkt_update_ip_checksums(struct VmxnetTxPkt *pkt) +{ + uint16_t csum; + uint32_t ph_raw_csum; + assert(pkt); + uint8_t gso_type = pkt->virt_hdr.gso_type & ~VIRTIO_NET_HDR_GSO_ECN; + struct ip_header *ip_hdr; + + if (VIRTIO_NET_HDR_GSO_TCPV4 != gso_type && + VIRTIO_NET_HDR_GSO_UDP != gso_type) { + return; + } + + ip_hdr = pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base; + + if (pkt->payload_len + pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len > + ETH_MAX_IP_DGRAM_LEN) { + return; + } + + ip_hdr->ip_len = cpu_to_be16(pkt->payload_len + + pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len); + + /* Calculate IP header checksum */ + ip_hdr->ip_sum = 0; + csum = net_raw_checksum((uint8_t *)ip_hdr, + pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len); + ip_hdr->ip_sum = cpu_to_be16(csum); + + /* Calculate IP pseudo header checksum */ + ph_raw_csum = eth_calc_pseudo_hdr_csum(ip_hdr, pkt->payload_len); + csum = cpu_to_be16(~net_checksum_finish(ph_raw_csum)); + iov_from_buf(&pkt->vec[VMXNET_TX_PKT_PL_START_FRAG], pkt->payload_frags, + pkt->virt_hdr.csum_offset, &csum, sizeof(csum)); +} + +static void vmxnet_tx_pkt_calculate_hdr_len(struct VmxnetTxPkt *pkt) +{ + pkt->hdr_len = pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_len + + pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len; +} + +static bool vmxnet_tx_pkt_parse_headers(struct VmxnetTxPkt *pkt) +{ + struct iovec *l2_hdr, *l3_hdr; + size_t bytes_read; + size_t full_ip6hdr_len; + uint16_t l3_proto; + + assert(pkt); + + l2_hdr = &pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG]; + l3_hdr = &pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG]; + + bytes_read = iov_to_buf(pkt->raw, pkt->raw_frags, 0, l2_hdr->iov_base, + ETH_MAX_L2_HDR_LEN); + if (bytes_read < ETH_MAX_L2_HDR_LEN) { + l2_hdr->iov_len = 0; + return false; + } else { + l2_hdr->iov_len = eth_get_l2_hdr_length(l2_hdr->iov_base); + } + + l3_proto = eth_get_l3_proto(l2_hdr->iov_base, l2_hdr->iov_len); + + switch (l3_proto) { + case ETH_P_IP: + l3_hdr->iov_base = g_malloc(ETH_MAX_IP4_HDR_LEN); + + bytes_read = iov_to_buf(pkt->raw, pkt->raw_frags, l2_hdr->iov_len, + l3_hdr->iov_base, sizeof(struct ip_header)); + + if (bytes_read < sizeof(struct ip_header)) { + l3_hdr->iov_len = 0; + return false; + } + + l3_hdr->iov_len = IP_HDR_GET_LEN(l3_hdr->iov_base); + pkt->l4proto = ((struct ip_header *) l3_hdr->iov_base)->ip_p; + + /* copy optional IPv4 header data */ + bytes_read = iov_to_buf(pkt->raw, pkt->raw_frags, + l2_hdr->iov_len + sizeof(struct ip_header), + l3_hdr->iov_base + sizeof(struct ip_header), + l3_hdr->iov_len - sizeof(struct ip_header)); + if (bytes_read < l3_hdr->iov_len - sizeof(struct ip_header)) { + l3_hdr->iov_len = 0; + return false; + } + break; + + case ETH_P_IPV6: + if (!eth_parse_ipv6_hdr(pkt->raw, pkt->raw_frags, l2_hdr->iov_len, + &pkt->l4proto, &full_ip6hdr_len)) { + l3_hdr->iov_len = 0; + return false; + } + + l3_hdr->iov_base = g_malloc(full_ip6hdr_len); + + bytes_read = iov_to_buf(pkt->raw, pkt->raw_frags, l2_hdr->iov_len, + l3_hdr->iov_base, full_ip6hdr_len); + + if (bytes_read < full_ip6hdr_len) { + l3_hdr->iov_len = 0; + return false; + } else { + l3_hdr->iov_len = full_ip6hdr_len; + } + break; + + default: + l3_hdr->iov_len = 0; + break; + } + + vmxnet_tx_pkt_calculate_hdr_len(pkt); + pkt->packet_type = get_eth_packet_type(l2_hdr->iov_base); + return true; +} + +static bool vmxnet_tx_pkt_rebuild_payload(struct VmxnetTxPkt *pkt) +{ + size_t payload_len = iov_size(pkt->raw, pkt->raw_frags) - pkt->hdr_len; + + pkt->payload_frags = iov_copy(&pkt->vec[VMXNET_TX_PKT_PL_START_FRAG], + pkt->max_payload_frags, + pkt->raw, pkt->raw_frags, + pkt->hdr_len, payload_len); + + if (pkt->payload_frags != (uint32_t) -1) { + pkt->payload_len = payload_len; + return true; + } else { + return false; + } +} + +bool vmxnet_tx_pkt_parse(struct VmxnetTxPkt *pkt) +{ + return vmxnet_tx_pkt_parse_headers(pkt) && + vmxnet_tx_pkt_rebuild_payload(pkt); +} + +struct virtio_net_hdr *vmxnet_tx_pkt_get_vhdr(struct VmxnetTxPkt *pkt) +{ + assert(pkt); + return &pkt->virt_hdr; +} + +static uint8_t vmxnet_tx_pkt_get_gso_type(struct VmxnetTxPkt *pkt, + bool tso_enable) +{ + uint8_t rc = VIRTIO_NET_HDR_GSO_NONE; + uint16_t l3_proto; + + l3_proto = eth_get_l3_proto(pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_base, + pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_len); + + if (!tso_enable) { + goto func_exit; + } + + rc = eth_get_gso_type(l3_proto, pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base, + pkt->l4proto); + +func_exit: + return rc; +} + +void vmxnet_tx_pkt_build_vheader(struct VmxnetTxPkt *pkt, bool tso_enable, + bool csum_enable, uint32_t gso_size) +{ + struct tcp_hdr l4hdr; + assert(pkt); + + /* csum has to be enabled if tso is. */ + assert(csum_enable || !tso_enable); + + pkt->virt_hdr.gso_type = vmxnet_tx_pkt_get_gso_type(pkt, tso_enable); + + switch (pkt->virt_hdr.gso_type & ~VIRTIO_NET_HDR_GSO_ECN) { + case VIRTIO_NET_HDR_GSO_NONE: + pkt->virt_hdr.hdr_len = 0; + pkt->virt_hdr.gso_size = 0; + break; + + case VIRTIO_NET_HDR_GSO_UDP: + pkt->virt_hdr.gso_size = IP_FRAG_ALIGN_SIZE(gso_size); + pkt->virt_hdr.hdr_len = pkt->hdr_len + sizeof(struct udp_header); + break; + + case VIRTIO_NET_HDR_GSO_TCPV4: + case VIRTIO_NET_HDR_GSO_TCPV6: + iov_to_buf(&pkt->vec[VMXNET_TX_PKT_PL_START_FRAG], pkt->payload_frags, + 0, &l4hdr, sizeof(l4hdr)); + pkt->virt_hdr.hdr_len = pkt->hdr_len + l4hdr.th_off * sizeof(uint32_t); + pkt->virt_hdr.gso_size = IP_FRAG_ALIGN_SIZE(gso_size); + break; + + default: + assert(false); + } + + if (csum_enable) { + switch (pkt->l4proto) { + case IP_PROTO_TCP: + pkt->virt_hdr.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; + pkt->virt_hdr.csum_start = pkt->hdr_len; + pkt->virt_hdr.csum_offset = offsetof(struct tcp_hdr, th_sum); + break; + case IP_PROTO_UDP: + pkt->virt_hdr.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; + pkt->virt_hdr.csum_start = pkt->hdr_len; + pkt->virt_hdr.csum_offset = offsetof(struct udp_hdr, uh_sum); + break; + default: + break; + } + } +} + +void vmxnet_tx_pkt_setup_vlan_header(struct VmxnetTxPkt *pkt, uint16_t vlan) +{ + bool is_new; + assert(pkt); + + eth_setup_vlan_headers(pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_base, + vlan, &is_new); + + /* update l2hdrlen */ + if (is_new) { + pkt->hdr_len += sizeof(struct vlan_header); + pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_len += + sizeof(struct vlan_header); + } +} + +bool vmxnet_tx_pkt_add_raw_fragment(struct VmxnetTxPkt *pkt, hwaddr pa, + size_t len) +{ + hwaddr mapped_len = 0; + struct iovec *ventry; + assert(pkt); + assert(pkt->max_raw_frags > pkt->raw_frags); + + if (!len) { + return true; + } + + ventry = &pkt->raw[pkt->raw_frags]; + mapped_len = len; + + ventry->iov_base = cpu_physical_memory_map(pa, &mapped_len, false); + ventry->iov_len = mapped_len; + pkt->raw_frags += !!ventry->iov_base; + + if ((ventry->iov_base == NULL) || (len != mapped_len)) { + return false; + } + + return true; +} + +eth_pkt_types_e vmxnet_tx_pkt_get_packet_type(struct VmxnetTxPkt *pkt) +{ + assert(pkt); + + return pkt->packet_type; +} + +size_t vmxnet_tx_pkt_get_total_len(struct VmxnetTxPkt *pkt) +{ + assert(pkt); + + return pkt->hdr_len + pkt->payload_len; +} + +void vmxnet_tx_pkt_dump(struct VmxnetTxPkt *pkt) +{ +#ifdef VMXNET_TX_PKT_DEBUG + assert(pkt); + + printf("TX PKT: hdr_len: %d, pkt_type: 0x%X, l2hdr_len: %lu, " + "l3hdr_len: %lu, payload_len: %u\n", pkt->hdr_len, pkt->packet_type, + pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_len, + pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len, pkt->payload_len); +#endif +} + +void vmxnet_tx_pkt_reset(struct VmxnetTxPkt *pkt) +{ + int i; + + /* no assert, as reset can be called before tx_pkt_init */ + if (!pkt) { + return; + } + + memset(&pkt->virt_hdr, 0, sizeof(pkt->virt_hdr)); + + g_free(pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base); + pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base = NULL; + + assert(pkt->vec); + for (i = VMXNET_TX_PKT_L2HDR_FRAG; + i < pkt->payload_frags + VMXNET_TX_PKT_PL_START_FRAG; i++) { + pkt->vec[i].iov_len = 0; + } + pkt->payload_len = 0; + pkt->payload_frags = 0; + + assert(pkt->raw); + for (i = 0; i < pkt->raw_frags; i++) { + assert(pkt->raw[i].iov_base); + cpu_physical_memory_unmap(pkt->raw[i].iov_base, pkt->raw[i].iov_len, + false, pkt->raw[i].iov_len); + pkt->raw[i].iov_len = 0; + } + pkt->raw_frags = 0; + + pkt->hdr_len = 0; + pkt->packet_type = 0; + pkt->l4proto = 0; +} + +static void vmxnet_tx_pkt_do_sw_csum(struct VmxnetTxPkt *pkt) +{ + struct iovec *iov = &pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG]; + uint32_t csum_cntr; + uint16_t csum = 0; + /* num of iovec without vhdr */ + uint32_t iov_len = pkt->payload_frags + VMXNET_TX_PKT_PL_START_FRAG - 1; + uint16_t csl; + struct ip_header *iphdr; + size_t csum_offset = pkt->virt_hdr.csum_start + pkt->virt_hdr.csum_offset; + + /* Put zero to checksum field */ + iov_from_buf(iov, iov_len, csum_offset, &csum, sizeof csum); + + /* Calculate L4 TCP/UDP checksum */ + csl = pkt->payload_len; + + /* data checksum */ + csum_cntr = + net_checksum_add_iov(iov, iov_len, pkt->virt_hdr.csum_start, csl); + /* add pseudo header to csum */ + iphdr = pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base; + csum_cntr += eth_calc_pseudo_hdr_csum(iphdr, csl); + + /* Put the checksum obtained into the packet */ + csum = cpu_to_be16(net_checksum_finish(csum_cntr)); + iov_from_buf(iov, iov_len, csum_offset, &csum, sizeof csum); +} + +enum { + VMXNET_TX_PKT_FRAGMENT_L2_HDR_POS = 0, + VMXNET_TX_PKT_FRAGMENT_L3_HDR_POS, + VMXNET_TX_PKT_FRAGMENT_HEADER_NUM +}; + +#define VMXNET_MAX_FRAG_SG_LIST (64) + +static size_t vmxnet_tx_pkt_fetch_fragment(struct VmxnetTxPkt *pkt, + int *src_idx, size_t *src_offset, struct iovec *dst, int *dst_idx) +{ + size_t fetched = 0; + struct iovec *src = pkt->vec; + + *dst_idx = VMXNET_TX_PKT_FRAGMENT_HEADER_NUM; + + while (fetched < pkt->virt_hdr.gso_size) { + + /* no more place in fragment iov */ + if (*dst_idx == VMXNET_MAX_FRAG_SG_LIST) { + break; + } + + /* no more data in iovec */ + if (*src_idx == (pkt->payload_frags + VMXNET_TX_PKT_PL_START_FRAG)) { + break; + } + + + dst[*dst_idx].iov_base = src[*src_idx].iov_base + *src_offset; + dst[*dst_idx].iov_len = MIN(src[*src_idx].iov_len - *src_offset, + pkt->virt_hdr.gso_size - fetched); + + *src_offset += dst[*dst_idx].iov_len; + fetched += dst[*dst_idx].iov_len; + + if (*src_offset == src[*src_idx].iov_len) { + *src_offset = 0; + (*src_idx)++; + } + + (*dst_idx)++; + } + + return fetched; +} + +static bool vmxnet_tx_pkt_do_sw_fragmentation(struct VmxnetTxPkt *pkt, + NetClientState *nc) +{ + struct iovec fragment[VMXNET_MAX_FRAG_SG_LIST]; + size_t fragment_len = 0; + bool more_frags = false; + + /* some pointers for shorter code */ + void *l2_iov_base, *l3_iov_base; + size_t l2_iov_len, l3_iov_len; + int src_idx = VMXNET_TX_PKT_PL_START_FRAG, dst_idx; + size_t src_offset = 0; + size_t fragment_offset = 0; + + l2_iov_base = pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_base; + l2_iov_len = pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_len; + l3_iov_base = pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base; + l3_iov_len = pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len; + + /* Copy headers */ + fragment[VMXNET_TX_PKT_FRAGMENT_L2_HDR_POS].iov_base = l2_iov_base; + fragment[VMXNET_TX_PKT_FRAGMENT_L2_HDR_POS].iov_len = l2_iov_len; + fragment[VMXNET_TX_PKT_FRAGMENT_L3_HDR_POS].iov_base = l3_iov_base; + fragment[VMXNET_TX_PKT_FRAGMENT_L3_HDR_POS].iov_len = l3_iov_len; + + + /* Put as much data as possible and send */ + do { + fragment_len = vmxnet_tx_pkt_fetch_fragment(pkt, &src_idx, &src_offset, + fragment, &dst_idx); + + more_frags = (fragment_offset + fragment_len < pkt->payload_len); + + eth_setup_ip4_fragmentation(l2_iov_base, l2_iov_len, l3_iov_base, + l3_iov_len, fragment_len, fragment_offset, more_frags); + + eth_fix_ip4_checksum(l3_iov_base, l3_iov_len); + + qemu_sendv_packet(nc, fragment, dst_idx); + + fragment_offset += fragment_len; + + } while (more_frags); + + return true; +} + +bool vmxnet_tx_pkt_send(struct VmxnetTxPkt *pkt, NetClientState *nc) +{ + assert(pkt); + + if (!pkt->has_virt_hdr && + pkt->virt_hdr.flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) { + vmxnet_tx_pkt_do_sw_csum(pkt); + } + + /* + * Since underlying infrastructure does not support IP datagrams longer + * than 64K we should drop such packets and don't even try to send + */ + if (VIRTIO_NET_HDR_GSO_NONE != pkt->virt_hdr.gso_type) { + if (pkt->payload_len > + ETH_MAX_IP_DGRAM_LEN - + pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len) { + return false; + } + } + + if (pkt->has_virt_hdr || + pkt->virt_hdr.gso_type == VIRTIO_NET_HDR_GSO_NONE) { + qemu_sendv_packet(nc, pkt->vec, + pkt->payload_frags + VMXNET_TX_PKT_PL_START_FRAG); + return true; + } + + return vmxnet_tx_pkt_do_sw_fragmentation(pkt, nc); +} diff --git a/hw/vmxnet_tx_pkt.h b/hw/vmxnet_tx_pkt.h new file mode 100644 index 0000000000..57121a6fe5 --- /dev/null +++ b/hw/vmxnet_tx_pkt.h @@ -0,0 +1,148 @@ +/* + * QEMU VMWARE VMXNET* paravirtual NICs - TX packets abstraction + * + * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com) + * + * Developed by Daynix Computing LTD (http://www.daynix.com) + * + * Authors: + * Dmitry Fleytman + * Tamir Shomer + * Yan Vugenfirer + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef VMXNET_TX_PKT_H +#define VMXNET_TX_PKT_H + +#include "stdint.h" +#include "stdbool.h" +#include "net/eth.h" +#include "exec/hwaddr.h" + +/* define to enable packet dump functions */ +/*#define VMXNET_TX_PKT_DEBUG*/ + +struct VmxnetTxPkt; + +/** + * Init function for tx packet functionality + * + * @pkt: packet pointer + * @max_frags: max tx ip fragments + * @has_virt_hdr: device uses virtio header. + */ +void vmxnet_tx_pkt_init(struct VmxnetTxPkt **pkt, uint32_t max_frags, + bool has_virt_hdr); + +/** + * Clean all tx packet resources. + * + * @pkt: packet. + */ +void vmxnet_tx_pkt_uninit(struct VmxnetTxPkt *pkt); + +/** + * get virtio header + * + * @pkt: packet + * @ret: virtio header + */ +struct virtio_net_hdr *vmxnet_tx_pkt_get_vhdr(struct VmxnetTxPkt *pkt); + +/** + * build virtio header (will be stored in module context) + * + * @pkt: packet + * @tso_enable: TSO enabled + * @csum_enable: CSO enabled + * @gso_size: MSS size for TSO + * + */ +void vmxnet_tx_pkt_build_vheader(struct VmxnetTxPkt *pkt, bool tso_enable, + bool csum_enable, uint32_t gso_size); + +/** + * updates vlan tag, and adds vlan header in case it is missing + * + * @pkt: packet + * @vlan: VLAN tag + * + */ +void vmxnet_tx_pkt_setup_vlan_header(struct VmxnetTxPkt *pkt, uint16_t vlan); + +/** + * populate data fragment into pkt context. + * + * @pkt: packet + * @pa: physical address of fragment + * @len: length of fragment + * + */ +bool vmxnet_tx_pkt_add_raw_fragment(struct VmxnetTxPkt *pkt, hwaddr pa, + size_t len); + +/** + * fix ip header fields and calculate checksums needed. + * + * @pkt: packet + * + */ +void vmxnet_tx_pkt_update_ip_checksums(struct VmxnetTxPkt *pkt); + +/** + * get length of all populated data. + * + * @pkt: packet + * @ret: total data length + * + */ +size_t vmxnet_tx_pkt_get_total_len(struct VmxnetTxPkt *pkt); + +/** + * get packet type + * + * @pkt: packet + * @ret: packet type + * + */ +eth_pkt_types_e vmxnet_tx_pkt_get_packet_type(struct VmxnetTxPkt *pkt); + +/** + * prints packet data if debug is enabled + * + * @pkt: packet + * + */ +void vmxnet_tx_pkt_dump(struct VmxnetTxPkt *pkt); + +/** + * reset tx packet private context (needed to be called between packets) + * + * @pkt: packet + * + */ +void vmxnet_tx_pkt_reset(struct VmxnetTxPkt *pkt); + +/** + * Send packet to qemu. handles sw offloads if vhdr is not supported. + * + * @pkt: packet + * @nc: NetClientState + * @ret: operation result + * + */ +bool vmxnet_tx_pkt_send(struct VmxnetTxPkt *pkt, NetClientState *nc); + +/** + * parse raw packet data and analyze offload requirements. + * + * @pkt: packet + * + */ +bool vmxnet_tx_pkt_parse(struct VmxnetTxPkt *pkt); + +#endif diff --git a/include/net/checksum.h b/include/net/checksum.h index 1f052986e6..80203fb6e0 100644 --- a/include/net/checksum.h +++ b/include/net/checksum.h @@ -20,10 +20,34 @@ #include -uint32_t net_checksum_add(int len, uint8_t *buf); +uint32_t net_checksum_add_cont(int len, uint8_t *buf, int seq); uint16_t net_checksum_finish(uint32_t sum); uint16_t net_checksum_tcpudp(uint16_t length, uint16_t proto, uint8_t *addrs, uint8_t *buf); void net_checksum_calculate(uint8_t *data, int length); +static inline uint32_t +net_checksum_add(int len, uint8_t *buf) +{ + return net_checksum_add_cont(len, buf, 0); +} + +static inline uint16_t +net_raw_checksum(uint8_t *data, int length) +{ + return net_checksum_finish(net_checksum_add(length, data)); +} + +/** + * net_checksum_add_iov: scatter-gather vector checksumming + * + * @iov: input scatter-gather array + * @iov_cnt: number of array elements + * @iov_off: starting iov offset for checksumming + * @size: length of data to be checksummed + */ +uint32_t net_checksum_add_iov(const struct iovec *iov, + const unsigned int iov_cnt, + uint32_t iov_off, uint32_t size); + #endif /* QEMU_NET_CHECKSUM_H */ diff --git a/include/net/eth.h b/include/net/eth.h new file mode 100644 index 0000000000..1d48e06b22 --- /dev/null +++ b/include/net/eth.h @@ -0,0 +1,347 @@ +/* + * QEMU network structures definitions and helper functions + * + * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com) + * + * Developed by Daynix Computing LTD (http://www.daynix.com) + * + * Portions developed by Free Software Foundation, Inc + * Copyright (C) 1991-1997, 2001, 2003, 2006 Free Software Foundation, Inc. + * See netinet/ip6.h and netinet/in.h (GNU C Library) + * + * Portions developed by Igor Kovalenko + * Copyright (c) 2006 Igor Kovalenko + * See hw/rtl8139.c (QEMU) + * + * Authors: + * Dmitry Fleytman + * Tamir Shomer + * Yan Vugenfirer + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef QEMU_ETH_H +#define QEMU_ETH_H + +#include +#include +#include "qemu/bswap.h" +#include "qemu/iov.h" + +#define ETH_ALEN 6 + +struct eth_header { + uint8_t h_dest[ETH_ALEN]; /* destination eth addr */ + uint8_t h_source[ETH_ALEN]; /* source ether addr */ + uint16_t h_proto; /* packet type ID field */ +}; + +struct vlan_header { + uint16_t h_tci; /* priority and VLAN ID */ + uint16_t h_proto; /* encapsulated protocol */ +}; + +struct ip_header { + uint8_t ip_ver_len; /* version and header length */ + uint8_t ip_tos; /* type of service */ + uint16_t ip_len; /* total length */ + uint16_t ip_id; /* identification */ + uint16_t ip_off; /* fragment offset field */ + uint8_t ip_ttl; /* time to live */ + uint8_t ip_p; /* protocol */ + uint16_t ip_sum; /* checksum */ + uint32_t ip_src, ip_dst; /* source and destination address */ +}; + +typedef struct tcp_header { + uint16_t th_sport; /* source port */ + uint16_t th_dport; /* destination port */ + uint32_t th_seq; /* sequence number */ + uint32_t th_ack; /* acknowledgment number */ + uint16_t th_offset_flags; /* data offset, reserved 6 bits, */ + /* TCP protocol flags */ + uint16_t th_win; /* window */ + uint16_t th_sum; /* checksum */ + uint16_t th_urp; /* urgent pointer */ +} tcp_header; + +typedef struct udp_header { + uint16_t uh_sport; /* source port */ + uint16_t uh_dport; /* destination port */ + uint16_t uh_ulen; /* udp length */ + uint16_t uh_sum; /* udp checksum */ +} udp_header; + +typedef struct ip_pseudo_header { + uint32_t ip_src; + uint32_t ip_dst; + uint8_t zeros; + uint8_t ip_proto; + uint16_t ip_payload; +} ip_pseudo_header; + +/* IPv6 address */ +struct in6_addr { + union { + uint8_t __u6_addr8[16]; + } __in6_u; +}; + +struct ip6_header { + union { + struct ip6_hdrctl { + uint32_t ip6_un1_flow; /* 4 bits version, 8 bits TC, + 20 bits flow-ID */ + uint16_t ip6_un1_plen; /* payload length */ + uint8_t ip6_un1_nxt; /* next header */ + uint8_t ip6_un1_hlim; /* hop limit */ + } ip6_un1; + uint8_t ip6_un2_vfc; /* 4 bits version, top 4 bits tclass */ + struct ip6_ecn_access { + uint8_t ip6_un3_vfc; /* 4 bits version, top 4 bits tclass */ + uint8_t ip6_un3_ecn; /* 2 bits ECN, top 6 bits payload length */ + } ip6_un3; + } ip6_ctlun; + struct in6_addr ip6_src; /* source address */ + struct in6_addr ip6_dst; /* destination address */ +}; + +struct ip6_ext_hdr { + uint8_t ip6r_nxt; /* next header */ + uint8_t ip6r_len; /* length in units of 8 octets */ +}; + +struct udp_hdr { + uint16_t uh_sport; /* source port */ + uint16_t uh_dport; /* destination port */ + uint16_t uh_ulen; /* udp length */ + uint16_t uh_sum; /* udp checksum */ +}; + +struct tcp_hdr { + u_short th_sport; /* source port */ + u_short th_dport; /* destination port */ + uint32_t th_seq; /* sequence number */ + uint32_t th_ack; /* acknowledgment number */ +#ifdef HOST_WORDS_BIGENDIAN + u_char th_off : 4, /* data offset */ + th_x2:4; /* (unused) */ +#else + u_char th_x2 : 4, /* (unused) */ + th_off:4; /* data offset */ +#endif + +#define TH_ELN 0x1 /* explicit loss notification */ +#define TH_ECN 0x2 /* explicit congestion notification */ +#define TH_FS 0x4 /* fast start */ + + u_char th_flags; +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 + u_short th_win; /* window */ + u_short th_sum; /* checksum */ + u_short th_urp; /* urgent pointer */ +}; + +#define ip6_nxt ip6_ctlun.ip6_un1.ip6_un1_nxt +#define ip6_ecn_acc ip6_ctlun.ip6_un3.ip6_un3_ecn + +#define PKT_GET_ETH_HDR(p) \ + ((struct eth_header *)(p)) +#define PKT_GET_VLAN_HDR(p) \ + ((struct vlan_header *) (((uint8_t *)(p)) + sizeof(struct eth_header))) +#define PKT_GET_DVLAN_HDR(p) \ + (PKT_GET_VLAN_HDR(p) + 1) +#define PKT_GET_IP_HDR(p) \ + ((struct ip_header *)(((uint8_t *)(p)) + eth_get_l2_hdr_length(p))) +#define IP_HDR_GET_LEN(p) \ + ((((struct ip_header *)p)->ip_ver_len & 0x0F) << 2) +#define PKT_GET_IP_HDR_LEN(p) \ + (IP_HDR_GET_LEN(PKT_GET_IP_HDR(p))) +#define PKT_GET_IP6_HDR(p) \ + ((struct ip6_header *) (((uint8_t *)(p)) + eth_get_l2_hdr_length(p))) +#define IP_HEADER_VERSION(ip) \ + ((ip->ip_ver_len >> 4)&0xf) + +#define ETH_P_IP (0x0800) +#define ETH_P_IPV6 (0x86dd) +#define ETH_P_VLAN (0x8100) +#define ETH_P_DVLAN (0x88a8) +#define VLAN_VID_MASK 0x0fff +#define IP_HEADER_VERSION_4 (4) +#define IP_HEADER_VERSION_6 (6) +#define IP_PROTO_TCP (6) +#define IP_PROTO_UDP (17) +#define IPTOS_ECN_MASK 0x03 +#define IPTOS_ECN(x) ((x) & IPTOS_ECN_MASK) +#define IPTOS_ECN_CE 0x03 +#define IP6_ECN_MASK 0xC0 +#define IP6_ECN(x) ((x) & IP6_ECN_MASK) +#define IP6_ECN_CE 0xC0 +#define IP4_DONT_FRAGMENT_FLAG (1 << 14) + +#define IS_SPECIAL_VLAN_ID(x) \ + (((x) == 0) || ((x) == 0xFFF)) + +#define ETH_MAX_L2_HDR_LEN \ + (sizeof(struct eth_header) + 2 * sizeof(struct vlan_header)) + +#define ETH_MAX_IP4_HDR_LEN (60) +#define ETH_MAX_IP_DGRAM_LEN (0xFFFF) + +#define IP_FRAG_UNIT_SIZE (8) +#define IP_FRAG_ALIGN_SIZE(x) ((x) & ~0x7) +#define IP_RF 0x8000 /* reserved fragment flag */ +#define IP_DF 0x4000 /* don't fragment flag */ +#define IP_MF 0x2000 /* more fragments flag */ +#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ + +#define IP6_EXT_GRANULARITY (8) /* Size granularity for + IPv6 extension headers */ + +/* IP6 extension header types */ +#define IP6_HOP_BY_HOP (0) +#define IP6_ROUTING (43) +#define IP6_FRAGMENT (44) +#define IP6_ESP (50) +#define IP6_AUTHENTICATION (51) +#define IP6_NONE (59) +#define IP6_DESTINATON (60) +#define IP6_MOBILITY (135) + +static inline int is_multicast_ether_addr(const uint8_t *addr) +{ + return 0x01 & addr[0]; +} + +static inline int is_broadcast_ether_addr(const uint8_t *addr) +{ + return (addr[0] & addr[1] & addr[2] & addr[3] & addr[4] & addr[5]) == 0xff; +} + +static inline int is_unicast_ether_addr(const uint8_t *addr) +{ + return !is_multicast_ether_addr(addr); +} + +typedef enum { + ETH_PKT_UCAST = 0xAABBCC00, + ETH_PKT_BCAST, + ETH_PKT_MCAST +} eth_pkt_types_e; + +static inline eth_pkt_types_e +get_eth_packet_type(const struct eth_header *ehdr) +{ + if (is_broadcast_ether_addr(ehdr->h_dest)) { + return ETH_PKT_BCAST; + } else if (is_multicast_ether_addr(ehdr->h_dest)) { + return ETH_PKT_MCAST; + } else { /* unicast */ + return ETH_PKT_UCAST; + } +} + +static inline uint32_t +eth_get_l2_hdr_length(const void *p) +{ + uint16_t proto = be16_to_cpu(PKT_GET_ETH_HDR(p)->h_proto); + struct vlan_header *hvlan = PKT_GET_VLAN_HDR(p); + switch (proto) { + case ETH_P_VLAN: + return sizeof(struct eth_header) + sizeof(struct vlan_header); + case ETH_P_DVLAN: + if (hvlan->h_proto == ETH_P_VLAN) { + return sizeof(struct eth_header) + 2 * sizeof(struct vlan_header); + } else { + return sizeof(struct eth_header) + sizeof(struct vlan_header); + } + default: + return sizeof(struct eth_header); + } +} + +static inline uint16_t +eth_get_pkt_tci(const void *p) +{ + uint16_t proto = be16_to_cpu(PKT_GET_ETH_HDR(p)->h_proto); + struct vlan_header *hvlan = PKT_GET_VLAN_HDR(p); + switch (proto) { + case ETH_P_VLAN: + case ETH_P_DVLAN: + return be16_to_cpu(hvlan->h_tci); + default: + return 0; + } +} + +static inline bool +eth_strip_vlan(const void *p, uint8_t *new_ehdr_buf, + uint16_t *payload_offset, uint16_t *tci) +{ + uint16_t proto = be16_to_cpu(PKT_GET_ETH_HDR(p)->h_proto); + struct vlan_header *hvlan = PKT_GET_VLAN_HDR(p); + struct eth_header *new_ehdr = (struct eth_header *) new_ehdr_buf; + + switch (proto) { + case ETH_P_VLAN: + case ETH_P_DVLAN: + memcpy(new_ehdr->h_source, PKT_GET_ETH_HDR(p)->h_source, ETH_ALEN); + memcpy(new_ehdr->h_dest, PKT_GET_ETH_HDR(p)->h_dest, ETH_ALEN); + new_ehdr->h_proto = hvlan->h_proto; + *tci = be16_to_cpu(hvlan->h_tci); + *payload_offset = + sizeof(struct eth_header) + sizeof(struct vlan_header); + if (be16_to_cpu(new_ehdr->h_proto) == ETH_P_VLAN) { + memcpy(PKT_GET_VLAN_HDR(new_ehdr), + PKT_GET_DVLAN_HDR(p), + sizeof(struct vlan_header)); + *payload_offset += sizeof(struct vlan_header); + } + return true; + default: + return false; + } +} + +static inline uint16_t +eth_get_l3_proto(const void *l2hdr, size_t l2hdr_len) +{ + uint8_t *proto_ptr = (uint8_t *) l2hdr + l2hdr_len - sizeof(uint16_t); + return be16_to_cpup((uint16_t *)proto_ptr); +} + +void eth_setup_vlan_headers(struct eth_header *ehdr, uint16_t vlan_tag, + bool *is_new); + +uint8_t eth_get_gso_type(uint16_t l3_proto, uint8_t *l3_hdr, uint8_t l4proto); + +void eth_get_protocols(const uint8_t *headers, + uint32_t hdr_length, + bool *isip4, bool *isip6, + bool *isudp, bool *istcp); + +void eth_setup_ip4_fragmentation(const void *l2hdr, size_t l2hdr_len, + void *l3hdr, size_t l3hdr_len, + size_t l3payload_len, + size_t frag_offset, bool more_frags); + +void +eth_fix_ip4_checksum(void *l3hdr, size_t l3hdr_len); + +uint32_t +eth_calc_pseudo_hdr_csum(struct ip_header *iphdr, uint16_t csl); + +bool +eth_parse_ipv6_hdr(struct iovec *pkt, int pkt_frags, + size_t ip6hdr_off, uint8_t *l4proto, + size_t *full_hdr_len); + +#endif diff --git a/include/net/net.h b/include/net/net.h index cb049a16a3..43d85a16eb 100644 --- a/include/net/net.h +++ b/include/net/net.h @@ -11,6 +11,11 @@ #define MAX_QUEUE_NUM 1024 +/* Maximum GSO packet size (64k) plus plenty of room for + * the ethernet and virtio_net headers + */ +#define NET_BUFSIZE (4096 + 65536) + struct MACAddr { uint8_t a[6]; }; diff --git a/net/Makefile.objs b/net/Makefile.objs index a08cd14e2e..4854a14fe4 100644 --- a/net/Makefile.objs +++ b/net/Makefile.objs @@ -1,6 +1,7 @@ common-obj-y = net.o queue.o checksum.o util.o hub.o common-obj-y += socket.o common-obj-y += dump.o +common-obj-y += eth.o common-obj-$(CONFIG_POSIX) += tap.o common-obj-$(CONFIG_LINUX) += tap-linux.o common-obj-$(CONFIG_WIN32) += tap-win32.o diff --git a/net/checksum.c b/net/checksum.c index 9919b2e5fd..14c08550e0 100644 --- a/net/checksum.c +++ b/net/checksum.c @@ -15,21 +15,23 @@ * along with this program; if not, see . */ +#include "qemu-common.h" #include "net/checksum.h" #define PROTO_TCP 6 #define PROTO_UDP 17 -uint32_t net_checksum_add(int len, uint8_t *buf) +uint32_t net_checksum_add_cont(int len, uint8_t *buf, int seq) { uint32_t sum = 0; int i; - for (i = 0; i < len; i++) { - if (i & 1) - sum += (uint32_t)buf[i]; - else - sum += (uint32_t)buf[i] << 8; + for (i = seq; i < seq + len; i++) { + if (i & 1) { + sum += (uint32_t)buf[i - seq]; + } else { + sum += (uint32_t)buf[i - seq] << 8; + } } return sum; } @@ -83,3 +85,31 @@ void net_checksum_calculate(uint8_t *data, int length) data[14+hlen+csum_offset] = csum >> 8; data[14+hlen+csum_offset+1] = csum & 0xff; } + +uint32_t +net_checksum_add_iov(const struct iovec *iov, const unsigned int iov_cnt, + uint32_t iov_off, uint32_t size) +{ + size_t iovec_off, buf_off; + unsigned int i; + uint32_t res = 0; + uint32_t seq = 0; + + iovec_off = 0; + buf_off = 0; + for (i = 0; i < iov_cnt && size; i++) { + if (iov_off < (iovec_off + iov[i].iov_len)) { + size_t len = MIN((iovec_off + iov[i].iov_len) - iov_off , size); + void *chunk_buf = iov[i].iov_base + (iov_off - iovec_off); + + res += net_checksum_add_cont(len, chunk_buf, seq); + seq += len; + + buf_off += len; + iov_off += len; + size -= len; + } + iovec_off += iov[i].iov_len; + } + return res; +} diff --git a/net/eth.c b/net/eth.c new file mode 100644 index 0000000000..1d7494d5e5 --- /dev/null +++ b/net/eth.c @@ -0,0 +1,217 @@ +/* + * QEMU network structures definitions and helper functions + * + * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com) + * + * Developed by Daynix Computing LTD (http://www.daynix.com) + * + * Authors: + * Dmitry Fleytman + * Tamir Shomer + * Yan Vugenfirer + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "net/eth.h" +#include "net/checksum.h" +#include "qemu-common.h" +#include "net/tap.h" + +void eth_setup_vlan_headers(struct eth_header *ehdr, uint16_t vlan_tag, + bool *is_new) +{ + struct vlan_header *vhdr = PKT_GET_VLAN_HDR(ehdr); + + switch (be16_to_cpu(ehdr->h_proto)) { + case ETH_P_VLAN: + case ETH_P_DVLAN: + /* vlan hdr exists */ + *is_new = false; + break; + + default: + /* No VLAN header, put a new one */ + vhdr->h_proto = ehdr->h_proto; + ehdr->h_proto = cpu_to_be16(ETH_P_VLAN); + *is_new = true; + break; + } + vhdr->h_tci = cpu_to_be16(vlan_tag); +} + +uint8_t +eth_get_gso_type(uint16_t l3_proto, uint8_t *l3_hdr, uint8_t l4proto) +{ + uint8_t ecn_state = 0; + + if (l3_proto == ETH_P_IP) { + struct ip_header *iphdr = (struct ip_header *) l3_hdr; + + if (IP_HEADER_VERSION(iphdr) == IP_HEADER_VERSION_4) { + if (IPTOS_ECN(iphdr->ip_tos) == IPTOS_ECN_CE) { + ecn_state = VIRTIO_NET_HDR_GSO_ECN; + } + if (l4proto == IP_PROTO_TCP) { + return VIRTIO_NET_HDR_GSO_TCPV4 | ecn_state; + } else if (l4proto == IP_PROTO_UDP) { + return VIRTIO_NET_HDR_GSO_UDP | ecn_state; + } + } + } else if (l3_proto == ETH_P_IPV6) { + struct ip6_header *ip6hdr = (struct ip6_header *) l3_hdr; + + if (IP6_ECN(ip6hdr->ip6_ecn_acc) == IP6_ECN_CE) { + ecn_state = VIRTIO_NET_HDR_GSO_ECN; + } + + if (l4proto == IP_PROTO_TCP) { + return VIRTIO_NET_HDR_GSO_TCPV6 | ecn_state; + } + } + + /* Unsupported offload */ + assert(false); + + return VIRTIO_NET_HDR_GSO_NONE | ecn_state; +} + +void eth_get_protocols(const uint8_t *headers, + uint32_t hdr_length, + bool *isip4, bool *isip6, + bool *isudp, bool *istcp) +{ + int proto; + size_t l2hdr_len = eth_get_l2_hdr_length(headers); + assert(hdr_length >= eth_get_l2_hdr_length(headers)); + *isip4 = *isip6 = *isudp = *istcp = false; + + proto = eth_get_l3_proto(headers, l2hdr_len); + if (proto == ETH_P_IP) { + *isip4 = true; + + struct ip_header *iphdr; + + assert(hdr_length >= + eth_get_l2_hdr_length(headers) + sizeof(struct ip_header)); + + iphdr = PKT_GET_IP_HDR(headers); + + if (IP_HEADER_VERSION(iphdr) == IP_HEADER_VERSION_4) { + if (iphdr->ip_p == IP_PROTO_TCP) { + *istcp = true; + } else if (iphdr->ip_p == IP_PROTO_UDP) { + *isudp = true; + } + } + } else if (proto == ETH_P_IPV6) { + uint8_t l4proto; + size_t full_ip6hdr_len; + + struct iovec hdr_vec; + hdr_vec.iov_base = (void *) headers; + hdr_vec.iov_len = hdr_length; + + *isip6 = true; + if (eth_parse_ipv6_hdr(&hdr_vec, 1, l2hdr_len, + &l4proto, &full_ip6hdr_len)) { + if (l4proto == IP_PROTO_TCP) { + *istcp = true; + } else if (l4proto == IP_PROTO_UDP) { + *isudp = true; + } + } + } +} + +void +eth_setup_ip4_fragmentation(const void *l2hdr, size_t l2hdr_len, + void *l3hdr, size_t l3hdr_len, + size_t l3payload_len, + size_t frag_offset, bool more_frags) +{ + if (eth_get_l3_proto(l2hdr, l2hdr_len) == ETH_P_IP) { + uint16_t orig_flags; + struct ip_header *iphdr = (struct ip_header *) l3hdr; + uint16_t frag_off_units = frag_offset / IP_FRAG_UNIT_SIZE; + uint16_t new_ip_off; + + assert(frag_offset % IP_FRAG_UNIT_SIZE == 0); + assert((frag_off_units & ~IP_OFFMASK) == 0); + + orig_flags = be16_to_cpu(iphdr->ip_off) & ~(IP_OFFMASK|IP_MF); + new_ip_off = frag_off_units | orig_flags | (more_frags ? IP_MF : 0); + iphdr->ip_off = cpu_to_be16(new_ip_off); + iphdr->ip_len = cpu_to_be16(l3payload_len + l3hdr_len); + } +} + +void +eth_fix_ip4_checksum(void *l3hdr, size_t l3hdr_len) +{ + struct ip_header *iphdr = (struct ip_header *) l3hdr; + iphdr->ip_sum = 0; + iphdr->ip_sum = cpu_to_be16(net_raw_checksum(l3hdr, l3hdr_len)); +} + +uint32_t +eth_calc_pseudo_hdr_csum(struct ip_header *iphdr, uint16_t csl) +{ + struct ip_pseudo_header ipph; + ipph.ip_src = iphdr->ip_src; + ipph.ip_dst = iphdr->ip_dst; + ipph.ip_payload = cpu_to_be16(csl); + ipph.ip_proto = iphdr->ip_p; + ipph.zeros = 0; + return net_checksum_add(sizeof(ipph), (uint8_t *) &ipph); +} + +static bool +eth_is_ip6_extension_header_type(uint8_t hdr_type) +{ + switch (hdr_type) { + case IP6_HOP_BY_HOP: + case IP6_ROUTING: + case IP6_FRAGMENT: + case IP6_ESP: + case IP6_AUTHENTICATION: + case IP6_DESTINATON: + case IP6_MOBILITY: + return true; + default: + return false; + } +} + +bool eth_parse_ipv6_hdr(struct iovec *pkt, int pkt_frags, + size_t ip6hdr_off, uint8_t *l4proto, + size_t *full_hdr_len) +{ + struct ip6_header ip6_hdr; + struct ip6_ext_hdr ext_hdr; + size_t bytes_read; + + bytes_read = iov_to_buf(pkt, pkt_frags, ip6hdr_off, + &ip6_hdr, sizeof(ip6_hdr)); + if (bytes_read < sizeof(ip6_hdr)) { + return false; + } + + *full_hdr_len = sizeof(struct ip6_header); + + if (!eth_is_ip6_extension_header_type(ip6_hdr.ip6_nxt)) { + *l4proto = ip6_hdr.ip6_nxt; + return true; + } + + do { + bytes_read = iov_to_buf(pkt, pkt_frags, ip6hdr_off + *full_hdr_len, + &ext_hdr, sizeof(ext_hdr)); + *full_hdr_len += (ext_hdr.ip6r_len + 1) * IP6_EXT_GRANULARITY; + } while (eth_is_ip6_extension_header_type(ext_hdr.ip6r_nxt)); + + *l4proto = ext_hdr.ip6r_nxt; + return true; +} diff --git a/net/net.c b/net/net.c index f3d67f8322..7869161d8d 100644 --- a/net/net.c +++ b/net/net.c @@ -497,7 +497,7 @@ ssize_t qemu_send_packet_raw(NetClientState *nc, const uint8_t *buf, int size) static ssize_t nc_sendv_compat(NetClientState *nc, const struct iovec *iov, int iovcnt) { - uint8_t buffer[4096]; + uint8_t buffer[NET_BUFSIZE]; size_t offset; offset = iov_to_buf(iov, iovcnt, 0, buffer, sizeof(buffer)); diff --git a/net/socket.c b/net/socket.c index d8b35a23cb..6c3752b88a 100644 --- a/net/socket.c +++ b/net/socket.c @@ -40,7 +40,7 @@ typedef struct NetSocketState { unsigned int index; unsigned int packet_len; unsigned int send_index; /* number of bytes sent (only SOCK_STREAM) */ - uint8_t buf[4096]; + uint8_t buf[NET_BUFSIZE]; struct sockaddr_in dgram_dst; /* contains inet host and port destination iff connectionless (SOCK_DGRAM) */ IOHandler *send_fn; /* differs between SOCK_STREAM/SOCK_DGRAM */ bool read_poll; /* waiting to receive data? */ @@ -146,7 +146,7 @@ static void net_socket_send(void *opaque) NetSocketState *s = opaque; int size, err; unsigned l; - uint8_t buf1[4096]; + uint8_t buf1[NET_BUFSIZE]; const uint8_t *buf; size = qemu_recv(s->fd, buf1, sizeof(buf1), 0); @@ -438,6 +438,9 @@ static NetSocketState *net_socket_fd_init_stream(NetClientState *peer, s->fd = fd; s->listen_fd = -1; + /* Disable Nagle algorithm on TCP sockets to reduce latency */ + socket_set_nodelay(fd); + if (is_connected) { net_socket_connect(s); } else { diff --git a/net/tap.c b/net/tap.c index ce796997a3..e7c84811ac 100644 --- a/net/tap.c +++ b/net/tap.c @@ -44,17 +44,12 @@ #include "hw/vhost_net.h" -/* Maximum GSO packet size (64k) plus plenty of room for - * the ethernet and virtio_net headers - */ -#define TAP_BUFSIZE (4096 + 65536) - typedef struct TAPState { NetClientState nc; int fd; char down_script[1024]; char down_script_arg[128]; - uint8_t buf[TAP_BUFSIZE]; + uint8_t buf[NET_BUFSIZE]; bool read_poll; bool write_poll; bool using_vnet_hdr; diff --git a/net/vde.c b/net/vde.c index 4dea32d07a..2a619fbc81 100644 --- a/net/vde.c +++ b/net/vde.c @@ -39,7 +39,7 @@ typedef struct VDEState { static void vde_to_qemu(void *opaque) { VDEState *s = opaque; - uint8_t buf[4096]; + uint8_t buf[NET_BUFSIZE]; int size; size = vde_recv(s->vde, (char *)buf, sizeof(buf), 0);