118 changes: 43 additions & 75 deletions net/eth.c
Expand Up @@ -137,8 +137,7 @@ _eth_tcp_has_data(bool is_ip4,
}

void eth_get_protocols(const struct iovec *iov, int iovcnt,
bool *isip4, bool *isip6,
bool *isudp, bool *istcp,
bool *hasip4, bool *hasip6,
size_t *l3hdr_off,
size_t *l4hdr_off,
size_t *l5hdr_off,
Expand All @@ -151,8 +150,10 @@ void eth_get_protocols(const struct iovec *iov, int iovcnt,
size_t l2hdr_len = eth_get_l2_hdr_length_iov(iov, iovcnt);
size_t input_size = iov_size(iov, iovcnt);
size_t copied;
uint8_t ip_p;

*isip4 = *isip6 = *isudp = *istcp = false;
*hasip4 = *hasip6 = false;
l4hdr_info->proto = ETH_L4_HDR_PROTO_INVALID;

proto = eth_get_l3_proto(iov, iovcnt, l2hdr_len);

Expand All @@ -166,68 +167,62 @@ void eth_get_protocols(const struct iovec *iov, int iovcnt,
}

copied = iov_to_buf(iov, iovcnt, l2hdr_len, iphdr, sizeof(*iphdr));

*isip4 = true;

if (copied < sizeof(*iphdr)) {
if (copied < sizeof(*iphdr) ||
IP_HEADER_VERSION(iphdr) != IP_HEADER_VERSION_4) {
return;
}

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;
}
}

*hasip4 = true;
ip_p = iphdr->ip_p;
ip4hdr_info->fragment = IP4_IS_FRAGMENT(iphdr);
*l4hdr_off = l2hdr_len + IP_HDR_GET_LEN(iphdr);

fragment = ip4hdr_info->fragment;
} else if (proto == ETH_P_IPV6) {

*isip6 = true;
if (eth_parse_ipv6_hdr(iov, iovcnt, l2hdr_len,
ip6hdr_info)) {
if (ip6hdr_info->l4proto == IP_PROTO_TCP) {
*istcp = true;
} else if (ip6hdr_info->l4proto == IP_PROTO_UDP) {
*isudp = true;
}
} else {
if (!eth_parse_ipv6_hdr(iov, iovcnt, l2hdr_len, ip6hdr_info)) {
return;
}

*hasip6 = true;
ip_p = ip6hdr_info->l4proto;
*l4hdr_off = l2hdr_len + ip6hdr_info->full_hdr_len;
fragment = ip6hdr_info->fragment;
} else {
return;
}

if (!fragment) {
if (*istcp) {
*istcp = _eth_copy_chunk(input_size,
iov, iovcnt,
*l4hdr_off, sizeof(l4hdr_info->hdr.tcp),
&l4hdr_info->hdr.tcp);

if (*istcp) {
*l5hdr_off = *l4hdr_off +
TCP_HEADER_DATA_OFFSET(&l4hdr_info->hdr.tcp);

l4hdr_info->has_tcp_data =
_eth_tcp_has_data(proto == ETH_P_IP,
&ip4hdr_info->ip4_hdr,
&ip6hdr_info->ip6_hdr,
*l4hdr_off - *l3hdr_off,
&l4hdr_info->hdr.tcp);
}
} else if (*isudp) {
*isudp = _eth_copy_chunk(input_size,
iov, iovcnt,
*l4hdr_off, sizeof(l4hdr_info->hdr.udp),
&l4hdr_info->hdr.udp);
if (fragment) {
return;
}

switch (ip_p) {
case IP_PROTO_TCP:
if (_eth_copy_chunk(input_size,
iov, iovcnt,
*l4hdr_off, sizeof(l4hdr_info->hdr.tcp),
&l4hdr_info->hdr.tcp)) {
l4hdr_info->proto = ETH_L4_HDR_PROTO_TCP;
*l5hdr_off = *l4hdr_off +
TCP_HEADER_DATA_OFFSET(&l4hdr_info->hdr.tcp);

l4hdr_info->has_tcp_data =
_eth_tcp_has_data(proto == ETH_P_IP,
&ip4hdr_info->ip4_hdr,
&ip6hdr_info->ip6_hdr,
*l4hdr_off - *l3hdr_off,
&l4hdr_info->hdr.tcp);
}
break;

case IP_PROTO_UDP:
if (_eth_copy_chunk(input_size,
iov, iovcnt,
*l4hdr_off, sizeof(l4hdr_info->hdr.udp),
&l4hdr_info->hdr.udp)) {
l4hdr_info->proto = ETH_L4_HDR_PROTO_UDP;
*l5hdr_off = *l4hdr_off + sizeof(l4hdr_info->hdr.udp);
}
break;
}
}

Expand Down Expand Up @@ -314,33 +309,6 @@ eth_strip_vlan_ex(const struct iovec *iov, int iovcnt, size_t iovoff,
return 0;
}

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)
{
const struct iovec l2vec = {
.iov_base = (void *) l2hdr,
.iov_len = l2hdr_len
};

if (eth_get_l3_proto(&l2vec, 1, 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)
{
Expand Down
18 changes: 18 additions & 0 deletions net/net.c
Expand Up @@ -513,6 +513,15 @@ bool qemu_has_vnet_hdr_len(NetClientState *nc, int len)
return nc->info->has_vnet_hdr_len(nc, len);
}

bool qemu_get_using_vnet_hdr(NetClientState *nc)
{
if (!nc || !nc->info->get_using_vnet_hdr) {
return false;
}

return nc->info->get_using_vnet_hdr(nc);
}

void qemu_using_vnet_hdr(NetClientState *nc, bool enable)
{
if (!nc || !nc->info->using_vnet_hdr) {
Expand All @@ -532,6 +541,15 @@ void qemu_set_offload(NetClientState *nc, int csum, int tso4, int tso6,
nc->info->set_offload(nc, csum, tso4, tso6, ecn, ufo);
}

int qemu_get_vnet_hdr_len(NetClientState *nc)
{
if (!nc || !nc->info->get_vnet_hdr_len) {
return 0;
}

return nc->info->get_vnet_hdr_len(nc);
}

void qemu_set_vnet_hdr_len(NetClientState *nc, int len)
{
if (!nc || !nc->info->set_vnet_hdr_len) {
Expand Down
16 changes: 16 additions & 0 deletions net/tap.c
Expand Up @@ -255,6 +255,13 @@ static bool tap_has_vnet_hdr_len(NetClientState *nc, int len)
return !!tap_probe_vnet_hdr_len(s->fd, len);
}

static int tap_get_vnet_hdr_len(NetClientState *nc)
{
TAPState *s = DO_UPCAST(TAPState, nc, nc);

return s->host_vnet_hdr_len;
}

static void tap_set_vnet_hdr_len(NetClientState *nc, int len)
{
TAPState *s = DO_UPCAST(TAPState, nc, nc);
Expand All @@ -268,6 +275,13 @@ static void tap_set_vnet_hdr_len(NetClientState *nc, int len)
s->host_vnet_hdr_len = len;
}

static bool tap_get_using_vnet_hdr(NetClientState *nc)
{
TAPState *s = DO_UPCAST(TAPState, nc, nc);

return s->using_vnet_hdr;
}

static void tap_using_vnet_hdr(NetClientState *nc, bool using_vnet_hdr)
{
TAPState *s = DO_UPCAST(TAPState, nc, nc);
Expand Down Expand Up @@ -372,8 +386,10 @@ static NetClientInfo net_tap_info = {
.has_ufo = tap_has_ufo,
.has_vnet_hdr = tap_has_vnet_hdr,
.has_vnet_hdr_len = tap_has_vnet_hdr_len,
.get_using_vnet_hdr = tap_get_using_vnet_hdr,
.using_vnet_hdr = tap_using_vnet_hdr,
.set_offload = tap_set_offload,
.get_vnet_hdr_len = tap_get_vnet_hdr_len,
.set_vnet_hdr_len = tap_set_vnet_hdr_len,
.set_vnet_le = tap_set_vnet_le,
.set_vnet_be = tap_set_vnet_be,
Expand Down
1 change: 1 addition & 0 deletions scripts/ci/org.centos/stream/8/x86_64/test-avocado
Expand Up @@ -30,6 +30,7 @@ make get-vm-images
tests/avocado/cpu_queries.py:QueryCPUModelExpansion.test \
tests/avocado/empty_cpu_model.py:EmptyCPUModel.test \
tests/avocado/hotplug_cpu.py:HotPlugCPU.test \
tests/avocado/igb.py:IGB.test \
tests/avocado/info_usernet.py:InfoUsernet.test_hostfwd \
tests/avocado/intel_iommu.py:IntelIOMMU.test_intel_iommu \
tests/avocado/intel_iommu.py:IntelIOMMU.test_intel_iommu_pt \
Expand Down
38 changes: 38 additions & 0 deletions tests/avocado/igb.py
@@ -0,0 +1,38 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# ethtool tests for igb registers, interrupts, etc

from avocado_qemu import LinuxTest

class IGB(LinuxTest):
"""
:avocado: tags=accel:kvm
:avocado: tags=arch:x86_64
:avocado: tags=distro:fedora
:avocado: tags=distro_version:31
:avocado: tags=machine:q35
"""

timeout = 180

def test(self):
self.require_accelerator('kvm')
kernel_url = self.distro.pxeboot_url + 'vmlinuz'
kernel_hash = '5b6f6876e1b5bda314f93893271da0d5777b1f3c'
kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
initrd_url = self.distro.pxeboot_url + 'initrd.img'
initrd_hash = 'dd0340a1b39bd28f88532babd4581c67649ec5b1'
initrd_path = self.fetch_asset(initrd_url, asset_hash=initrd_hash)

# Ideally we want to test MSI as well, but it is blocked by a bug
# fixed with:
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=28e96556baca7056d11d9fb3cdd0aba4483e00d8
kernel_params = self.distro.default_kernel_params + ' pci=nomsi'

self.vm.add_args('-kernel', kernel_path,
'-initrd', initrd_path,
'-append', kernel_params,
'-accel', 'kvm',
'-device', 'igb')
self.launch_and_wait()
self.ssh_command('dnf -y install ethtool')
self.ssh_command('ethtool -t eth1 offline')
25 changes: 15 additions & 10 deletions tests/qtest/e1000e-test.c
Expand Up @@ -27,6 +27,7 @@
#include "qemu/osdep.h"
#include "libqtest-single.h"
#include "libqos/pci-pc.h"
#include "net/eth.h"
#include "qemu/sockets.h"
#include "qemu/iov.h"
#include "qemu/module.h"
Expand All @@ -35,17 +36,21 @@
#include "libqos/e1000e.h"
#include "hw/net/e1000_regs.h"

static const struct eth_header packet = {
.h_dest = E1000E_ADDRESS,
.h_source = E1000E_ADDRESS,
};

static void e1000e_send_verify(QE1000E *d, int *test_sockets, QGuestAllocator *alloc)
{
static const char test[] = "TEST";
struct e1000_tx_desc descr;
char buffer[64];
int ret;
uint32_t recv_len;

/* Prepare test data buffer */
uint64_t data = guest_alloc(alloc, sizeof(buffer));
memwrite(data, test, sizeof(test));
memwrite(data, &packet, sizeof(packet));

/* Prepare TX descriptor */
memset(&descr, 0, sizeof(descr));
Expand All @@ -71,7 +76,7 @@ static void e1000e_send_verify(QE1000E *d, int *test_sockets, QGuestAllocator *a
g_assert_cmpint(ret, == , sizeof(recv_len));
ret = recv(test_sockets[0], buffer, sizeof(buffer), 0);
g_assert_cmpint(ret, ==, sizeof(buffer));
g_assert_cmpstr(buffer, == , test);
g_assert_false(memcmp(buffer, &packet, sizeof(packet)));

/* Free test data buffer */
guest_free(alloc, data);
Expand All @@ -81,24 +86,24 @@ static void e1000e_receive_verify(QE1000E *d, int *test_sockets, QGuestAllocator
{
union e1000_rx_desc_extended descr;

char test[] = "TEST";
int len = htonl(sizeof(test));
struct eth_header test_iov = packet;
int len = htonl(sizeof(packet));
struct iovec iov[] = {
{
.iov_base = &len,
.iov_len = sizeof(len),
},{
.iov_base = test,
.iov_len = sizeof(test),
.iov_base = &test_iov,
.iov_len = sizeof(packet),
},
};

char buffer[64];
int ret;

/* Send a dummy packet to device's socket*/
ret = iov_send(test_sockets[0], iov, 2, 0, sizeof(len) + sizeof(test));
g_assert_cmpint(ret, == , sizeof(test) + sizeof(len));
ret = iov_send(test_sockets[0], iov, 2, 0, sizeof(len) + sizeof(packet));
g_assert_cmpint(ret, == , sizeof(packet) + sizeof(len));

/* Prepare test data buffer */
uint64_t data = guest_alloc(alloc, sizeof(buffer));
Expand All @@ -119,7 +124,7 @@ static void e1000e_receive_verify(QE1000E *d, int *test_sockets, QGuestAllocator

/* Check data sent to the backend */
memread(data, buffer, sizeof(buffer));
g_assert_cmpstr(buffer, == , test);
g_assert_false(memcmp(buffer, &packet, sizeof(packet)));

/* Free test data buffer */
guest_free(alloc, data);
Expand Down
5 changes: 5 additions & 0 deletions tests/qtest/fuzz/generic_fuzz_configs.h
Expand Up @@ -90,6 +90,11 @@ const generic_fuzz_config predefined_configs[] = {
.args = "-M q35 -nodefaults "
"-device e1000e,netdev=net0 -netdev user,id=net0",
.objects = "e1000e",
},{
.name = "igb",
.args = "-M q35 -nodefaults "
"-device igb,netdev=net0 -netdev user,id=net0",
.objects = "igb",
},{
.name = "cirrus-vga",
.args = "-machine q35 -nodefaults -device cirrus-vga",
Expand Down
243 changes: 243 additions & 0 deletions tests/qtest/igb-test.c
@@ -0,0 +1,243 @@
/*
* QTest testcase for igb NIC
*
* Copyright (c) 2022-2023 Red Hat, Inc.
* Copyright (c) 2015 Ravello Systems LTD (http://ravellosystems.com)
* Developed by Daynix Computing LTD (http://www.daynix.com)
*
* Authors:
* Akihiko Odaki <akihiko.odaki@daynix.com>
* Dmitry Fleytman <dmitry@daynix.com>
* Leonid Bloch <leonid@daynix.com>
* Yan Vugenfirer <yan@daynix.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/


#include "qemu/osdep.h"
#include "libqtest-single.h"
#include "libqos/pci-pc.h"
#include "net/eth.h"
#include "qemu/sockets.h"
#include "qemu/iov.h"
#include "qemu/module.h"
#include "qemu/bitops.h"
#include "libqos/libqos-malloc.h"
#include "libqos/e1000e.h"
#include "hw/net/igb_regs.h"

static const struct eth_header packet = {
.h_dest = E1000E_ADDRESS,
.h_source = E1000E_ADDRESS,
};

static void igb_send_verify(QE1000E *d, int *test_sockets, QGuestAllocator *alloc)
{
union e1000_adv_tx_desc descr;
char buffer[64];
int ret;
uint32_t recv_len;

/* Prepare test data buffer */
uint64_t data = guest_alloc(alloc, sizeof(buffer));
memwrite(data, &packet, sizeof(packet));

/* Prepare TX descriptor */
memset(&descr, 0, sizeof(descr));
descr.read.buffer_addr = cpu_to_le64(data);
descr.read.cmd_type_len = cpu_to_le32(E1000_TXD_CMD_RS |
E1000_TXD_CMD_EOP |
E1000_TXD_DTYP_D |
sizeof(buffer));

/* Put descriptor to the ring */
e1000e_tx_ring_push(d, &descr);

/* Wait for TX WB interrupt */
e1000e_wait_isr(d, E1000E_TX0_MSG_ID);

/* Check DD bit */
g_assert_cmphex(le32_to_cpu(descr.wb.status) & E1000_TXD_STAT_DD, ==,
E1000_TXD_STAT_DD);

/* Check data sent to the backend */
ret = recv(test_sockets[0], &recv_len, sizeof(recv_len), 0);
g_assert_cmpint(ret, == , sizeof(recv_len));
ret = recv(test_sockets[0], buffer, sizeof(buffer), 0);
g_assert_cmpint(ret, ==, sizeof(buffer));
g_assert_false(memcmp(buffer, &packet, sizeof(packet)));

/* Free test data buffer */
guest_free(alloc, data);
}

static void igb_receive_verify(QE1000E *d, int *test_sockets, QGuestAllocator *alloc)
{
union e1000_adv_rx_desc descr;

struct eth_header test_iov = packet;
int len = htonl(sizeof(packet));
struct iovec iov[] = {
{
.iov_base = &len,
.iov_len = sizeof(len),
},{
.iov_base = &test_iov,
.iov_len = sizeof(packet),
},
};

char buffer[64];
int ret;

/* Send a dummy packet to device's socket*/
ret = iov_send(test_sockets[0], iov, 2, 0, sizeof(len) + sizeof(packet));
g_assert_cmpint(ret, == , sizeof(packet) + sizeof(len));

/* Prepare test data buffer */
uint64_t data = guest_alloc(alloc, sizeof(buffer));

/* Prepare RX descriptor */
memset(&descr, 0, sizeof(descr));
descr.read.pkt_addr = cpu_to_le64(data);

/* Put descriptor to the ring */
e1000e_rx_ring_push(d, &descr);

/* Wait for TX WB interrupt */
e1000e_wait_isr(d, E1000E_RX0_MSG_ID);

/* Check DD bit */
g_assert_cmphex(le32_to_cpu(descr.wb.upper.status_error) &
E1000_RXD_STAT_DD, ==, E1000_RXD_STAT_DD);

/* Check data sent to the backend */
memread(data, buffer, sizeof(buffer));
g_assert_false(memcmp(buffer, &packet, sizeof(packet)));

/* Free test data buffer */
guest_free(alloc, data);
}

static void test_e1000e_init(void *obj, void *data, QGuestAllocator * alloc)
{
/* init does nothing */
}

static void test_igb_tx(void *obj, void *data, QGuestAllocator * alloc)
{
QE1000E_PCI *e1000e = obj;
QE1000E *d = &e1000e->e1000e;
QOSGraphObject *e_object = obj;
QPCIDevice *dev = e_object->get_driver(e_object, "pci-device");

/* FIXME: add spapr support */
if (qpci_check_buggy_msi(dev)) {
return;
}

igb_send_verify(d, data, alloc);
}

static void test_igb_rx(void *obj, void *data, QGuestAllocator * alloc)
{
QE1000E_PCI *e1000e = obj;
QE1000E *d = &e1000e->e1000e;
QOSGraphObject *e_object = obj;
QPCIDevice *dev = e_object->get_driver(e_object, "pci-device");

/* FIXME: add spapr support */
if (qpci_check_buggy_msi(dev)) {
return;
}

igb_receive_verify(d, data, alloc);
}

static void test_igb_multiple_transfers(void *obj, void *data,
QGuestAllocator *alloc)
{
static const long iterations = 4 * 1024;
long i;

QE1000E_PCI *e1000e = obj;
QE1000E *d = &e1000e->e1000e;
QOSGraphObject *e_object = obj;
QPCIDevice *dev = e_object->get_driver(e_object, "pci-device");

/* FIXME: add spapr support */
if (qpci_check_buggy_msi(dev)) {
return;
}

for (i = 0; i < iterations; i++) {
igb_send_verify(d, data, alloc);
igb_receive_verify(d, data, alloc);
}

}

static void test_igb_hotplug(void *obj, void *data, QGuestAllocator * alloc)
{
QTestState *qts = global_qtest; /* TODO: get rid of global_qtest here */
QE1000E_PCI *dev = obj;

if (dev->pci_dev.bus->not_hotpluggable) {
g_test_skip("pci bus does not support hotplug");
return;
}

qtest_qmp_device_add(qts, "igb", "igb_net", "{'addr': '0x06'}");
qpci_unplug_acpi_device_test(qts, "igb_net", 0x06);
}

static void data_test_clear(void *sockets)
{
int *test_sockets = sockets;

close(test_sockets[0]);
qos_invalidate_command_line();
close(test_sockets[1]);
g_free(test_sockets);
}

static void *data_test_init(GString *cmd_line, void *arg)
{
int *test_sockets = g_new(int, 2);
int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, test_sockets);
g_assert_cmpint(ret, != , -1);

g_string_append_printf(cmd_line, " -netdev socket,fd=%d,id=hs0 ",
test_sockets[1]);

g_test_queue_destroy(data_test_clear, test_sockets);
return test_sockets;
}

static void register_igb_test(void)
{
QOSGraphTestOptions opts = {
.before = data_test_init,
};

qos_add_test("init", "igb", test_e1000e_init, &opts);
qos_add_test("tx", "igb", test_igb_tx, &opts);
qos_add_test("rx", "igb", test_igb_rx, &opts);
qos_add_test("multiple_transfers", "igb",
test_igb_multiple_transfers, &opts);
qos_add_test("hotplug", "igb", test_igb_hotplug, &opts);
}

libqos_init(register_igb_test);
12 changes: 0 additions & 12 deletions tests/qtest/libqos/e1000e.c
Expand Up @@ -36,18 +36,6 @@

#define E1000E_RING_LEN (0x1000)

static void e1000e_macreg_write(QE1000E *d, uint32_t reg, uint32_t val)
{
QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e);
qpci_io_writel(&d_pci->pci_dev, d_pci->mac_regs, reg, val);
}

static uint32_t e1000e_macreg_read(QE1000E *d, uint32_t reg)
{
QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e);
return qpci_io_readl(&d_pci->pci_dev, d_pci->mac_regs, reg);
}

void e1000e_tx_ring_push(QE1000E *d, void *descr)
{
QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e);
Expand Down
14 changes: 14 additions & 0 deletions tests/qtest/libqos/e1000e.h
Expand Up @@ -25,6 +25,8 @@
#define E1000E_RX0_MSG_ID (0)
#define E1000E_TX0_MSG_ID (1)

#define E1000E_ADDRESS { 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 }

typedef struct QE1000E QE1000E;
typedef struct QE1000E_PCI QE1000E_PCI;

Expand All @@ -40,6 +42,18 @@ struct QE1000E_PCI {
QE1000E e1000e;
};

static inline void e1000e_macreg_write(QE1000E *d, uint32_t reg, uint32_t val)
{
QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e);
qpci_io_writel(&d_pci->pci_dev, d_pci->mac_regs, reg, val);
}

static inline uint32_t e1000e_macreg_read(QE1000E *d, uint32_t reg)
{
QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e);
return qpci_io_readl(&d_pci->pci_dev, d_pci->mac_regs, reg);
}

void e1000e_wait_isr(QE1000E *d, uint16_t msg_id);
void e1000e_tx_ring_push(QE1000E *d, void *descr);
void e1000e_rx_ring_push(QE1000E *d, void *descr);
Expand Down
185 changes: 185 additions & 0 deletions tests/qtest/libqos/igb.c
@@ -0,0 +1,185 @@
/*
* libqos driver framework
*
* Copyright (c) 2022-2023 Red Hat, Inc.
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library 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. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>
*/

#include "qemu/osdep.h"
#include "hw/net/igb_regs.h"
#include "hw/net/mii.h"
#include "hw/pci/pci_ids.h"
#include "../libqtest.h"
#include "pci-pc.h"
#include "qemu/sockets.h"
#include "qemu/iov.h"
#include "qemu/module.h"
#include "qemu/bitops.h"
#include "libqos-malloc.h"
#include "qgraph.h"
#include "e1000e.h"

#define IGB_IVAR_TEST_CFG \
((E1000E_RX0_MSG_ID | E1000_IVAR_VALID) << (igb_ivar_entry_rx(0) * 8) | \
((E1000E_TX0_MSG_ID | E1000_IVAR_VALID) << (igb_ivar_entry_tx(0) * 8)))

#define E1000E_RING_LEN (0x1000)

static void e1000e_foreach_callback(QPCIDevice *dev, int devfn, void *data)
{
QPCIDevice *res = data;
memcpy(res, dev, sizeof(QPCIDevice));
g_free(dev);
}

static void e1000e_pci_destructor(QOSGraphObject *obj)
{
QE1000E_PCI *epci = (QE1000E_PCI *) obj;
qpci_iounmap(&epci->pci_dev, epci->mac_regs);
qpci_msix_disable(&epci->pci_dev);
}

static void igb_pci_start_hw(QOSGraphObject *obj)
{
static const uint8_t address[] = E1000E_ADDRESS;
QE1000E_PCI *d = (QE1000E_PCI *) obj;
uint32_t val;

/* Enable the device */
qpci_device_enable(&d->pci_dev);

/* Reset the device */
val = e1000e_macreg_read(&d->e1000e, E1000_CTRL);
e1000e_macreg_write(&d->e1000e, E1000_CTRL, val | E1000_CTRL_RST | E1000_CTRL_SLU);

/* Setup link */
e1000e_macreg_write(&d->e1000e, E1000_MDIC,
MII_BMCR_AUTOEN | MII_BMCR_ANRESTART |
(MII_BMCR << E1000_MDIC_REG_SHIFT) |
(1 << E1000_MDIC_PHY_SHIFT) |
E1000_MDIC_OP_WRITE);

qtest_clock_step(d->pci_dev.bus->qts, 900000000);

/* Enable and configure MSI-X */
qpci_msix_enable(&d->pci_dev);
e1000e_macreg_write(&d->e1000e, E1000_IVAR0, IGB_IVAR_TEST_CFG);

/* Check the device link status */
val = e1000e_macreg_read(&d->e1000e, E1000_STATUS);
g_assert_cmphex(val & E1000_STATUS_LU, ==, E1000_STATUS_LU);

/* Initialize TX/RX logic */
e1000e_macreg_write(&d->e1000e, E1000_RCTL, 0);
e1000e_macreg_write(&d->e1000e, E1000_TCTL, 0);

e1000e_macreg_write(&d->e1000e, E1000_TDBAL(0),
(uint32_t) d->e1000e.tx_ring);
e1000e_macreg_write(&d->e1000e, E1000_TDBAH(0),
(uint32_t) (d->e1000e.tx_ring >> 32));
e1000e_macreg_write(&d->e1000e, E1000_TDLEN(0), E1000E_RING_LEN);
e1000e_macreg_write(&d->e1000e, E1000_TDT(0), 0);
e1000e_macreg_write(&d->e1000e, E1000_TDH(0), 0);

/* Enable transmit */
e1000e_macreg_write(&d->e1000e, E1000_TCTL, E1000_TCTL_EN);

e1000e_macreg_write(&d->e1000e, E1000_RDBAL(0),
(uint32_t)d->e1000e.rx_ring);
e1000e_macreg_write(&d->e1000e, E1000_RDBAH(0),
(uint32_t)(d->e1000e.rx_ring >> 32));
e1000e_macreg_write(&d->e1000e, E1000_RDLEN(0), E1000E_RING_LEN);
e1000e_macreg_write(&d->e1000e, E1000_RDT(0), 0);
e1000e_macreg_write(&d->e1000e, E1000_RDH(0), 0);
e1000e_macreg_write(&d->e1000e, E1000_RA,
le32_to_cpu(*(uint32_t *)address));
e1000e_macreg_write(&d->e1000e, E1000_RA + 4,
E1000_RAH_AV | E1000_RAH_POOL_1 |
le16_to_cpu(*(uint16_t *)(address + 4)));

/* Enable receive */
e1000e_macreg_write(&d->e1000e, E1000_RFCTL, E1000_RFCTL_EXTEN);
e1000e_macreg_write(&d->e1000e, E1000_RCTL, E1000_RCTL_EN);

/* Enable all interrupts */
e1000e_macreg_write(&d->e1000e, E1000_IMS, 0xFFFFFFFF);
e1000e_macreg_write(&d->e1000e, E1000_EIMS, 0xFFFFFFFF);

}

static void *igb_pci_get_driver(void *obj, const char *interface)
{
QE1000E_PCI *epci = obj;
if (!g_strcmp0(interface, "igb-if")) {
return &epci->e1000e;
}

/* implicit contains */
if (!g_strcmp0(interface, "pci-device")) {
return &epci->pci_dev;
}

fprintf(stderr, "%s not present in igb\n", interface);
g_assert_not_reached();
}

static void *igb_pci_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
{
QE1000E_PCI *d = g_new0(QE1000E_PCI, 1);
QPCIBus *bus = pci_bus;
QPCIAddress *address = addr;

qpci_device_foreach(bus, address->vendor_id, address->device_id,
e1000e_foreach_callback, &d->pci_dev);

/* Map BAR0 (mac registers) */
d->mac_regs = qpci_iomap(&d->pci_dev, 0, NULL);

/* Allocate and setup TX ring */
d->e1000e.tx_ring = guest_alloc(alloc, E1000E_RING_LEN);
g_assert(d->e1000e.tx_ring != 0);

/* Allocate and setup RX ring */
d->e1000e.rx_ring = guest_alloc(alloc, E1000E_RING_LEN);
g_assert(d->e1000e.rx_ring != 0);

d->obj.get_driver = igb_pci_get_driver;
d->obj.start_hw = igb_pci_start_hw;
d->obj.destructor = e1000e_pci_destructor;

return &d->obj;
}

static void igb_register_nodes(void)
{
QPCIAddress addr = {
.vendor_id = PCI_VENDOR_ID_INTEL,
.device_id = E1000_DEV_ID_82576,
};

/*
* FIXME: every test using this node needs to setup a -netdev socket,id=hs0
* otherwise QEMU is not going to start
*/
QOSGraphEdgeOptions opts = {
.extra_device_opts = "netdev=hs0",
};
add_qpci_address(&opts, &addr);

qos_node_create_driver("igb", igb_pci_create);
qos_node_consumes("igb", "pci-bus", &opts);
}

libqos_init(igb_register_nodes);
1 change: 1 addition & 0 deletions tests/qtest/libqos/meson.build
Expand Up @@ -30,6 +30,7 @@ libqos_srcs = files(
'i2c.c',
'i2c-imx.c',
'i2c-omap.c',
'igb.c',
'sdhci.c',
'tpci200.c',
'virtio.c',
Expand Down
1 change: 1 addition & 0 deletions tests/qtest/meson.build
Expand Up @@ -259,6 +259,7 @@ qos_test_ss.add(
'virtio-scsi-test.c',
'virtio-iommu-test.c',
'vmxnet3-test.c',
'igb-test.c',
)

if config_all_devices.has_key('CONFIG_VIRTIO_SERIAL')
Expand Down
8 changes: 5 additions & 3 deletions tools/ebpf/Makefile.ebpf
@@ -1,21 +1,23 @@
OBJS = rss.bpf.o

LLC ?= llc
LLVM_STRIP ?= llvm-strip
CLANG ?= clang
INC_FLAGS = `$(CLANG) -print-file-name=include`
EXTRA_CFLAGS ?= -O2 -emit-llvm -fno-stack-protector
EXTRA_CFLAGS ?= -O2 -g -target bpf

all: $(OBJS)

.PHONY: clean

clean:
rm -f $(OBJS)
rm -f rss.bpf.skeleton.h

$(OBJS): %.o:%.c
$(CLANG) $(INC_FLAGS) \
-D__KERNEL__ -D__ASM_SYSREG_H \
-I../include $(LINUXINCLUDE) \
$(EXTRA_CFLAGS) -c $< -o -| $(LLC) -march=bpf -filetype=obj -o $@
$(EXTRA_CFLAGS) -c $< -o $@
$(LLVM_STRIP) -g $@
bpftool gen skeleton rss.bpf.o > rss.bpf.skeleton.h
cp rss.bpf.skeleton.h ../../ebpf/
43 changes: 20 additions & 23 deletions tools/ebpf/rss.bpf.c
Expand Up @@ -76,29 +76,26 @@ struct packet_hash_info_t {
};
};

struct bpf_map_def SEC("maps")
tap_rss_map_configurations = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(struct rss_config_t),
.max_entries = 1,
};

struct bpf_map_def SEC("maps")
tap_rss_map_toeplitz_key = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(struct toeplitz_key_data_t),
.max_entries = 1,
};

struct bpf_map_def SEC("maps")
tap_rss_map_indirection_table = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u16),
.max_entries = INDIRECTION_TABLE_SIZE,
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(struct rss_config_t));
__uint(max_entries, 1);
} tap_rss_map_configurations SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(struct toeplitz_key_data_t));
__uint(max_entries, 1);
} tap_rss_map_toeplitz_key SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u16));
__uint(max_entries, INDIRECTION_TABLE_SIZE);
} tap_rss_map_indirection_table SEC(".maps");

static inline void net_rx_rss_add_chunk(__u8 *rss_input, size_t *bytes_written,
const void *ptr, size_t size) {
Expand Down