Merge tag 'mm-nonmm-stable-2025-12-06-11-14' of git://git.kernel.org/pub/scm/linux...
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 6 Dec 2025 22:01:20 +0000 (14:01 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 6 Dec 2025 22:01:20 +0000 (14:01 -0800)
Pull non-MM updates from Andrew Morton:

 - "panic: sys_info: Refactor and fix a potential issue" (Andy Shevchenko)
   fixes a build issue and does some cleanup in ib/sys_info.c

 - "Implement mul_u64_u64_div_u64_roundup()" (David Laight)
   enhances the 64-bit math code on behalf of a PWM driver and beefs up
   the test module for these library functions

 - "scripts/gdb/symbols: make BPF debug info available to GDB" (Ilya Leoshkevich)
   makes BPF symbol names, sizes, and line numbers available to the GDB
   debugger

 - "Enable hung_task and lockup cases to dump system info on demand" (Feng Tang)
   adds a sysctl which can be used to cause additional info dumping when
   the hung-task and lockup detectors fire

 - "lib/base64: add generic encoder/decoder, migrate users" (Kuan-Wei Chiu)
   adds a general base64 encoder/decoder to lib/ and migrates several
   users away from their private implementations

 - "rbree: inline rb_first() and rb_last()" (Eric Dumazet)
   makes TCP a little faster

 - "liveupdate: Rework KHO for in-kernel users" (Pasha Tatashin)
   reworks the KEXEC Handover interfaces in preparation for Live Update
   Orchestrator (LUO), and possibly for other future clients

 - "kho: simplify state machine and enable dynamic updates" (Pasha Tatashin)
   increases the flexibility of KEXEC Handover. Also preparation for LUO

 - "Live Update Orchestrator" (Pasha Tatashin)
   is a major new feature targeted at cloud environments. Quoting the
   cover letter:

      This series introduces the Live Update Orchestrator, a kernel
      subsystem designed to facilitate live kernel updates using a
      kexec-based reboot. This capability is critical for cloud
      environments, allowing hypervisors to be updated with minimal
      downtime for running virtual machines. LUO achieves this by
      preserving the state of selected resources, such as memory,
      devices and their dependencies, across the kernel transition.

      As a key feature, this series includes support for preserving
      memfd file descriptors, which allows critical in-memory data, such
      as guest RAM or any other large memory region, to be maintained in
      RAM across the kexec reboot.

   Mike Rappaport merits a mention here, for his extensive review and
   testing work.

 - "kexec: reorganize kexec and kdump sysfs" (Sourabh Jain)
   moves the kexec and kdump sysfs entries from /sys/kernel/ to
   /sys/kernel/kexec/ and adds back-compatibility symlinks which can
   hopefully be removed one day

 - "kho: fixes for vmalloc restoration" (Mike Rapoport)
   fixes a BUG which was being hit during KHO restoration of vmalloc()
   regions

* tag 'mm-nonmm-stable-2025-12-06-11-14' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: (139 commits)
  calibrate: update header inclusion
  Reinstate "resource: avoid unnecessary lookups in find_next_iomem_res()"
  vmcoreinfo: track and log recoverable hardware errors
  kho: fix restoring of contiguous ranges of order-0 pages
  kho: kho_restore_vmalloc: fix initialization of pages array
  MAINTAINERS: TPM DEVICE DRIVER: update the W-tag
  init: replace simple_strtoul with kstrtoul to improve lpj_setup
  KHO: fix boot failure due to kmemleak access to non-PRESENT pages
  Documentation/ABI: new kexec and kdump sysfs interface
  Documentation/ABI: mark old kexec sysfs deprecated
  kexec: move sysfs entries to /sys/kernel/kexec
  test_kho: always print restore status
  kho: free chunks using free_page() instead of kfree()
  selftests/liveupdate: add kexec test for multiple and empty sessions
  selftests/liveupdate: add simple kexec-based selftest for LUO
  selftests/liveupdate: add userspace API selftests
  docs: add documentation for memfd preservation via LUO
  mm: memfd_luo: allow preserving memfd
  liveupdate: luo_file: add private argument to store runtime state
  mm: shmem: export some functions to internal.h
  ...

66 files changed:
1  2 
.mailmap
Documentation/admin-guide/kernel-parameters.txt
Documentation/dev-tools/checkpatch.rst
Documentation/driver-api/index.rst
MAINTAINERS
arch/x86/kernel/cpu/mce/core.c
drivers/acpi/apei/ghes.c
fs/ceph/crypto.c
fs/ceph/dir.c
fs/ceph/inode.c
fs/ocfs2/inode.c
include/linux/compiler.h
include/linux/shmem_fs.h
include/linux/uaccess.h
init/Kconfig
ipc/namespace.c
kernel/exit.c
kernel/fork.c
kernel/module/main.c
kernel/panic.c
kernel/watchdog.c
lib/Kconfig.debug
mm/Makefile
mm/internal.h
mm/memblock.c
mm/shmem.c
tools/testing/selftests/alsa/conf.c
tools/testing/selftests/arm64/fp/fp-ptrace.c
tools/testing/selftests/arm64/fp/sve-ptrace.c
tools/testing/selftests/bpf/xskxceiver.c
tools/testing/selftests/cgroup/test_core.c
tools/testing/selftests/cgroup/test_cpu.c
tools/testing/selftests/cgroup/test_cpuset.c
tools/testing/selftests/cgroup/test_freezer.c
tools/testing/selftests/cgroup/test_kill.c
tools/testing/selftests/cgroup/test_kmem.c
tools/testing/selftests/cgroup/test_memcontrol.c
tools/testing/selftests/cgroup/test_zswap.c
tools/testing/selftests/coredump/stackdump_test.c
tools/testing/selftests/drivers/net/gro.c
tools/testing/selftests/drivers/net/hw/toeplitz.c
tools/testing/selftests/filesystems/utils.c
tools/testing/selftests/iommu/iommufd_utils.h
tools/testing/selftests/mm/guard-regions.c
tools/testing/selftests/mm/gup_test.c
tools/testing/selftests/mm/hmm-tests.c
tools/testing/selftests/mm/ksm_functional_tests.c
tools/testing/selftests/mm/mremap_test.c
tools/testing/selftests/mm/soft-dirty.c
tools/testing/selftests/mm/vm_util.c
tools/testing/selftests/mm/vm_util.h
tools/testing/selftests/namespaces/nsid_test.c
tools/testing/selftests/net/netlink-dumps.c
tools/testing/selftests/net/tls.c
tools/testing/selftests/pidfd/pidfd.h
tools/testing/selftests/pidfd/pidfd_info_test.c
tools/testing/selftests/riscv/hwprobe/cbo.c
tools/testing/selftests/timers/nanosleep.c
tools/testing/selftests/timers/posix_timers.c
tools/testing/selftests/vfio/lib/vfio_pci_device.c
tools/testing/selftests/vfio/lib/vfio_pci_driver.c
tools/testing/selftests/vfio/vfio_dma_mapping_test.c
tools/testing/selftests/vfio/vfio_iommufd_setup_test.c
tools/testing/selftests/vfio/vfio_pci_device_test.c
tools/testing/selftests/vfio/vfio_pci_driver_test.c
tools/testing/selftests/x86/test_vsyscall.c

diff --cc .mailmap
Simple merge
Simple merge
diff --cc MAINTAINERS
@@@ -14564,9 -14464,24 +14566,25 @@@ F: include/linux/livepatch*.
  F:    kernel/livepatch/
  F:    kernel/module/livepatch.c
  F:    samples/livepatch/
 +F:    scripts/livepatch/
  F:    tools/testing/selftests/livepatch/
  
+ LIVE UPDATE
+ M:    Pasha Tatashin <pasha.tatashin@soleen.com>
+ M:    Mike Rapoport <rppt@kernel.org>
+ R:    Pratyush Yadav <pratyush@kernel.org>
+ L:    linux-kernel@vger.kernel.org
+ S:    Maintained
+ F:    Documentation/core-api/liveupdate.rst
+ F:    Documentation/mm/memfd_preservation.rst
+ F:    Documentation/userspace-api/liveupdate.rst
+ F:    include/linux/liveupdate.h
+ F:    include/linux/liveupdate/
+ F:    include/uapi/linux/liveupdate.h
+ F:    kernel/liveupdate/
+ F:    mm/memfd_luo.c
+ F:    tools/testing/selftests/liveupdate/
  LLC (802.2)
  L:    netdev@vger.kernel.org
  S:    Odd fixes
@@@ -15668,10 -15572,8 +15686,10 @@@ F: include/media/imx.
  MEDIA DRIVERS FOR FREESCALE IMX7/8
  M:    Rui Miguel Silva <rmfrfs@gmail.com>
  M:    Laurent Pinchart <laurent.pinchart@ideasonboard.com>
- M:    Martin Kepplinger <martin.kepplinger@puri.sm>
+ M:    Martin Kepplinger-Novakovic <martink@posteo.de>
  R:    Purism Kernel Team <kernel@puri.sm>
 +R:    Frank Li <Frank.Li@nxp.com>
 +L:    imx@lists.linux.dev
  L:    linux-media@vger.kernel.org
  S:    Maintained
  T:    git git://linuxtv.org/media.git
Simple merge
Simple merge
Simple merge
diff --cc fs/ceph/dir.c
Simple merge
diff --cc fs/ceph/inode.c
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc init/Kconfig
Simple merge
diff --cc ipc/namespace.c
Simple merge
diff --cc kernel/exit.c
@@@ -251,13 -251,11 +251,11 @@@ repeat
        memset(&post, 0, sizeof(post));
  
        /* don't need to get the RCU readlock here - the process is dead and
-        * can't be modifying its own credentials. But shut RCU-lockdep up */
-       rcu_read_lock();
+        * can't be modifying its own credentials. */
        dec_rlimit_ucounts(task_ucounts(p), UCOUNT_RLIMIT_NPROC, 1);
-       rcu_read_unlock();
  
        pidfs_exit(p);
 -      cgroup_release(p);
 +      cgroup_task_release(p);
  
        /* Retrieve @thread_pid before __unhash_process() may set it to NULL. */
        thread_pid = task_pid(p);
diff --cc kernel/fork.c
Simple merge
Simple merge
diff --cc kernel/panic.c
Simple merge
Simple merge
Simple merge
diff --cc mm/Makefile
Simple merge
diff --cc mm/internal.h
Simple merge
diff --cc mm/memblock.c
Simple merge
diff --cc mm/shmem.c
Simple merge
Simple merge
index 995b492,0000000..e894037
mode 100644,000000..100644
--- /dev/null
@@@ -1,1369 -1,0 +1,1369 @@@
- #include "../../kselftest.h"
 +// SPDX-License-Identifier: GPL-2.0
 +/*
 + * This testsuite provides conformance testing for GRO coalescing.
 + *
 + * Test cases:
 + * 1.data
 + *  Data packets of the same size and same header setup with correct
 + *  sequence numbers coalesce. The one exception being the last data
 + *  packet coalesced: it can be smaller than the rest and coalesced
 + *  as long as it is in the same flow.
 + * 2.ack
 + *  Pure ACK does not coalesce.
 + * 3.flags
 + *  Specific test cases: no packets with PSH, SYN, URG, RST set will
 + *  be coalesced.
 + * 4.tcp
 + *  Packets with incorrect checksum, non-consecutive seqno and
 + *  different TCP header options shouldn't coalesce. Nit: given that
 + *  some extension headers have paddings, such as timestamp, headers
 + *  that are padding differently would not be coalesced.
 + * 5.ip:
 + *  Packets with different (ECN, TTL, TOS) header, ip options or
 + *  ip fragments (ipv6) shouldn't coalesce.
 + * 6.large:
 + *  Packets larger than GRO_MAX_SIZE packets shouldn't coalesce.
 + *
 + * MSS is defined as 4096 - header because if it is too small
 + * (i.e. 1500 MTU - header), it will result in many packets,
 + * increasing the "large" test case's flakiness. This is because
 + * due to time sensitivity in the coalescing window, the receiver
 + * may not coalesce all of the packets.
 + *
 + * Note the timing issue applies to all of the test cases, so some
 + * flakiness is to be expected.
 + *
 + */
 +
 +#define _GNU_SOURCE
 +
 +#include <arpa/inet.h>
 +#include <errno.h>
 +#include <error.h>
 +#include <getopt.h>
 +#include <linux/filter.h>
 +#include <linux/if_packet.h>
 +#include <linux/ipv6.h>
 +#include <net/ethernet.h>
 +#include <net/if.h>
 +#include <netinet/in.h>
 +#include <netinet/ip.h>
 +#include <netinet/ip6.h>
 +#include <netinet/tcp.h>
 +#include <stdbool.h>
 +#include <stddef.h>
 +#include <stdio.h>
 +#include <stdarg.h>
 +#include <string.h>
 +#include <unistd.h>
 +
++#include "kselftest.h"
 +#include "../../net/lib/ksft.h"
 +
 +#define DPORT 8000
 +#define SPORT 1500
 +#define PAYLOAD_LEN 100
 +#define NUM_PACKETS 4
 +#define START_SEQ 100
 +#define START_ACK 100
 +#define ETH_P_NONE 0
 +#define TOTAL_HDR_LEN (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr))
 +#define MSS (4096 - sizeof(struct tcphdr) - sizeof(struct ipv6hdr))
 +#define MAX_PAYLOAD (IP_MAXPACKET - sizeof(struct tcphdr) - sizeof(struct ipv6hdr))
 +#define NUM_LARGE_PKT (MAX_PAYLOAD / MSS)
 +#define MAX_HDR_LEN (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr))
 +#define MIN_EXTHDR_SIZE 8
 +#define EXT_PAYLOAD_1 "\x00\x00\x00\x00\x00\x00"
 +#define EXT_PAYLOAD_2 "\x11\x11\x11\x11\x11\x11"
 +
 +#define ipv6_optlen(p)  (((p)->hdrlen+1) << 3) /* calculate IPv6 extension header len */
 +#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
 +
 +static const char *addr6_src = "fdaa::2";
 +static const char *addr6_dst = "fdaa::1";
 +static const char *addr4_src = "192.168.1.200";
 +static const char *addr4_dst = "192.168.1.100";
 +static int proto = -1;
 +static uint8_t src_mac[ETH_ALEN], dst_mac[ETH_ALEN];
 +static char *testname = "data";
 +static char *ifname = "eth0";
 +static char *smac = "aa:00:00:00:00:02";
 +static char *dmac = "aa:00:00:00:00:01";
 +static bool verbose;
 +static bool tx_socket = true;
 +static int tcp_offset = -1;
 +static int total_hdr_len = -1;
 +static int ethhdr_proto = -1;
 +static bool ipip;
 +static const int num_flush_id_cases = 6;
 +
 +static void vlog(const char *fmt, ...)
 +{
 +      va_list args;
 +
 +      if (verbose) {
 +              va_start(args, fmt);
 +              vfprintf(stderr, fmt, args);
 +              va_end(args);
 +      }
 +}
 +
 +static void setup_sock_filter(int fd)
 +{
 +      const int dport_off = tcp_offset + offsetof(struct tcphdr, dest);
 +      const int ethproto_off = offsetof(struct ethhdr, h_proto);
 +      int optlen = 0;
 +      int ipproto_off, opt_ipproto_off;
 +      int next_off;
 +
 +      if (ipip)
 +              next_off = sizeof(struct iphdr) + offsetof(struct iphdr, protocol);
 +      else if (proto == PF_INET)
 +              next_off = offsetof(struct iphdr, protocol);
 +      else
 +              next_off = offsetof(struct ipv6hdr, nexthdr);
 +      ipproto_off = ETH_HLEN + next_off;
 +
 +      /* Overridden later if exthdrs are used: */
 +      opt_ipproto_off = ipproto_off;
 +
 +      if (strcmp(testname, "ip") == 0) {
 +              if (proto == PF_INET)
 +                      optlen = sizeof(struct ip_timestamp);
 +              else {
 +                      BUILD_BUG_ON(sizeof(struct ip6_hbh) > MIN_EXTHDR_SIZE);
 +                      BUILD_BUG_ON(sizeof(struct ip6_dest) > MIN_EXTHDR_SIZE);
 +                      BUILD_BUG_ON(sizeof(struct ip6_frag) > MIN_EXTHDR_SIZE);
 +
 +                      /* same size for HBH and Fragment extension header types */
 +                      optlen = MIN_EXTHDR_SIZE;
 +                      opt_ipproto_off = ETH_HLEN + sizeof(struct ipv6hdr)
 +                              + offsetof(struct ip6_ext, ip6e_nxt);
 +              }
 +      }
 +
 +      /* this filter validates the following:
 +       *      - packet is IPv4/IPv6 according to the running test.
 +       *      - packet is TCP. Also handles the case of one extension header and then TCP.
 +       *      - checks the packet tcp dport equals to DPORT. Also handles the case of one
 +       *        extension header and then TCP.
 +       */
 +      struct sock_filter filter[] = {
 +                      BPF_STMT(BPF_LD  + BPF_H   + BPF_ABS, ethproto_off),
 +                      BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ntohs(ethhdr_proto), 0, 9),
 +                      BPF_STMT(BPF_LD  + BPF_B   + BPF_ABS, ipproto_off),
 +                      BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_TCP, 2, 0),
 +                      BPF_STMT(BPF_LD  + BPF_B   + BPF_ABS, opt_ipproto_off),
 +                      BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_TCP, 0, 5),
 +                      BPF_STMT(BPF_LD  + BPF_H   + BPF_ABS, dport_off),
 +                      BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DPORT, 2, 0),
 +                      BPF_STMT(BPF_LD  + BPF_H   + BPF_ABS, dport_off + optlen),
 +                      BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DPORT, 0, 1),
 +                      BPF_STMT(BPF_RET + BPF_K, 0xFFFFFFFF),
 +                      BPF_STMT(BPF_RET + BPF_K, 0),
 +      };
 +
 +      struct sock_fprog bpf = {
 +              .len = ARRAY_SIZE(filter),
 +              .filter = filter,
 +      };
 +
 +      if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)) < 0)
 +              error(1, errno, "error setting filter");
 +}
 +
 +static uint32_t checksum_nofold(void *data, size_t len, uint32_t sum)
 +{
 +      uint16_t *words = data;
 +      int i;
 +
 +      for (i = 0; i < len / 2; i++)
 +              sum += words[i];
 +      if (len & 1)
 +              sum += ((char *)data)[len - 1];
 +      return sum;
 +}
 +
 +static uint16_t checksum_fold(void *data, size_t len, uint32_t sum)
 +{
 +      sum = checksum_nofold(data, len, sum);
 +      while (sum > 0xFFFF)
 +              sum = (sum & 0xFFFF) + (sum >> 16);
 +      return ~sum;
 +}
 +
 +static uint16_t tcp_checksum(void *buf, int payload_len)
 +{
 +      struct pseudo_header6 {
 +              struct in6_addr saddr;
 +              struct in6_addr daddr;
 +              uint16_t protocol;
 +              uint16_t payload_len;
 +      } ph6;
 +      struct pseudo_header4 {
 +              struct in_addr saddr;
 +              struct in_addr daddr;
 +              uint16_t protocol;
 +              uint16_t payload_len;
 +      } ph4;
 +      uint32_t sum = 0;
 +
 +      if (proto == PF_INET6) {
 +              if (inet_pton(AF_INET6, addr6_src, &ph6.saddr) != 1)
 +                      error(1, errno, "inet_pton6 source ip pseudo");
 +              if (inet_pton(AF_INET6, addr6_dst, &ph6.daddr) != 1)
 +                      error(1, errno, "inet_pton6 dest ip pseudo");
 +              ph6.protocol = htons(IPPROTO_TCP);
 +              ph6.payload_len = htons(sizeof(struct tcphdr) + payload_len);
 +
 +              sum = checksum_nofold(&ph6, sizeof(ph6), 0);
 +      } else if (proto == PF_INET) {
 +              if (inet_pton(AF_INET, addr4_src, &ph4.saddr) != 1)
 +                      error(1, errno, "inet_pton source ip pseudo");
 +              if (inet_pton(AF_INET, addr4_dst, &ph4.daddr) != 1)
 +                      error(1, errno, "inet_pton dest ip pseudo");
 +              ph4.protocol = htons(IPPROTO_TCP);
 +              ph4.payload_len = htons(sizeof(struct tcphdr) + payload_len);
 +
 +              sum = checksum_nofold(&ph4, sizeof(ph4), 0);
 +      }
 +
 +      return checksum_fold(buf, sizeof(struct tcphdr) + payload_len, sum);
 +}
 +
 +static void read_MAC(uint8_t *mac_addr, char *mac)
 +{
 +      if (sscanf(mac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
 +                 &mac_addr[0], &mac_addr[1], &mac_addr[2],
 +                 &mac_addr[3], &mac_addr[4], &mac_addr[5]) != 6)
 +              error(1, 0, "sscanf");
 +}
 +
 +static void fill_datalinklayer(void *buf)
 +{
 +      struct ethhdr *eth = buf;
 +
 +      memcpy(eth->h_dest, dst_mac, ETH_ALEN);
 +      memcpy(eth->h_source, src_mac, ETH_ALEN);
 +      eth->h_proto = ethhdr_proto;
 +}
 +
 +static void fill_networklayer(void *buf, int payload_len, int protocol)
 +{
 +      struct ipv6hdr *ip6h = buf;
 +      struct iphdr *iph = buf;
 +
 +      if (proto == PF_INET6) {
 +              memset(ip6h, 0, sizeof(*ip6h));
 +
 +              ip6h->version = 6;
 +              ip6h->payload_len = htons(sizeof(struct tcphdr) + payload_len);
 +              ip6h->nexthdr = protocol;
 +              ip6h->hop_limit = 8;
 +              if (inet_pton(AF_INET6, addr6_src, &ip6h->saddr) != 1)
 +                      error(1, errno, "inet_pton source ip6");
 +              if (inet_pton(AF_INET6, addr6_dst, &ip6h->daddr) != 1)
 +                      error(1, errno, "inet_pton dest ip6");
 +      } else if (proto == PF_INET) {
 +              memset(iph, 0, sizeof(*iph));
 +
 +              iph->version = 4;
 +              iph->ihl = 5;
 +              iph->ttl = 8;
 +              iph->protocol   = protocol;
 +              iph->tot_len = htons(sizeof(struct tcphdr) +
 +                              payload_len + sizeof(struct iphdr));
 +              iph->frag_off = htons(0x4000); /* DF = 1, MF = 0 */
 +              if (inet_pton(AF_INET, addr4_src, &iph->saddr) != 1)
 +                      error(1, errno, "inet_pton source ip");
 +              if (inet_pton(AF_INET, addr4_dst, &iph->daddr) != 1)
 +                      error(1, errno, "inet_pton dest ip");
 +              iph->check = checksum_fold(buf, sizeof(struct iphdr), 0);
 +      }
 +}
 +
 +static void fill_transportlayer(void *buf, int seq_offset, int ack_offset,
 +                              int payload_len, int fin)
 +{
 +      struct tcphdr *tcph = buf;
 +
 +      memset(tcph, 0, sizeof(*tcph));
 +
 +      tcph->source = htons(SPORT);
 +      tcph->dest = htons(DPORT);
 +      tcph->seq = ntohl(START_SEQ + seq_offset);
 +      tcph->ack_seq = ntohl(START_ACK + ack_offset);
 +      tcph->ack = 1;
 +      tcph->fin = fin;
 +      tcph->doff = 5;
 +      tcph->window = htons(TCP_MAXWIN);
 +      tcph->urg_ptr = 0;
 +      tcph->check = tcp_checksum(tcph, payload_len);
 +}
 +
 +static void write_packet(int fd, char *buf, int len, struct sockaddr_ll *daddr)
 +{
 +      int ret = -1;
 +
 +      ret = sendto(fd, buf, len, 0, (struct sockaddr *)daddr, sizeof(*daddr));
 +      if (ret == -1)
 +              error(1, errno, "sendto failure");
 +      if (ret != len)
 +              error(1, errno, "sendto wrong length");
 +}
 +
 +static void create_packet(void *buf, int seq_offset, int ack_offset,
 +                        int payload_len, int fin)
 +{
 +      memset(buf, 0, total_hdr_len);
 +      memset(buf + total_hdr_len, 'a', payload_len);
 +
 +      fill_transportlayer(buf + tcp_offset, seq_offset, ack_offset,
 +                          payload_len, fin);
 +
 +      if (ipip) {
 +              fill_networklayer(buf + ETH_HLEN, payload_len + sizeof(struct iphdr),
 +                                IPPROTO_IPIP);
 +              fill_networklayer(buf + ETH_HLEN + sizeof(struct iphdr),
 +                                payload_len, IPPROTO_TCP);
 +      } else {
 +              fill_networklayer(buf + ETH_HLEN, payload_len, IPPROTO_TCP);
 +      }
 +
 +      fill_datalinklayer(buf);
 +}
 +
 +/* send one extra flag, not first and not last pkt */
 +static void send_flags(int fd, struct sockaddr_ll *daddr, int psh, int syn,
 +                     int rst, int urg)
 +{
 +      static char flag_buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      int payload_len, pkt_size, flag, i;
 +      struct tcphdr *tcph;
 +
 +      payload_len = PAYLOAD_LEN * psh;
 +      pkt_size = total_hdr_len + payload_len;
 +      flag = NUM_PACKETS / 2;
 +
 +      create_packet(flag_buf, flag * payload_len, 0, payload_len, 0);
 +
 +      tcph = (struct tcphdr *)(flag_buf + tcp_offset);
 +      tcph->psh = psh;
 +      tcph->syn = syn;
 +      tcph->rst = rst;
 +      tcph->urg = urg;
 +      tcph->check = 0;
 +      tcph->check = tcp_checksum(tcph, payload_len);
 +
 +      for (i = 0; i < NUM_PACKETS + 1; i++) {
 +              if (i == flag) {
 +                      write_packet(fd, flag_buf, pkt_size, daddr);
 +                      continue;
 +              }
 +              create_packet(buf, i * PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
 +              write_packet(fd, buf, total_hdr_len + PAYLOAD_LEN, daddr);
 +      }
 +}
 +
 +/* Test for data of same length, smaller than previous
 + * and of different lengths
 + */
 +static void send_data_pkts(int fd, struct sockaddr_ll *daddr,
 +                         int payload_len1, int payload_len2)
 +{
 +      static char buf[ETH_HLEN + IP_MAXPACKET];
 +
 +      create_packet(buf, 0, 0, payload_len1, 0);
 +      write_packet(fd, buf, total_hdr_len + payload_len1, daddr);
 +      create_packet(buf, payload_len1, 0, payload_len2, 0);
 +      write_packet(fd, buf, total_hdr_len + payload_len2, daddr);
 +}
 +
 +/* If incoming segments make tracked segment length exceed
 + * legal IP datagram length, do not coalesce
 + */
 +static void send_large(int fd, struct sockaddr_ll *daddr, int remainder)
 +{
 +      static char pkts[NUM_LARGE_PKT][TOTAL_HDR_LEN + MSS];
 +      static char last[TOTAL_HDR_LEN + MSS];
 +      static char new_seg[TOTAL_HDR_LEN + MSS];
 +      int i;
 +
 +      for (i = 0; i < NUM_LARGE_PKT; i++)
 +              create_packet(pkts[i], i * MSS, 0, MSS, 0);
 +      create_packet(last, NUM_LARGE_PKT * MSS, 0, remainder, 0);
 +      create_packet(new_seg, (NUM_LARGE_PKT + 1) * MSS, 0, remainder, 0);
 +
 +      for (i = 0; i < NUM_LARGE_PKT; i++)
 +              write_packet(fd, pkts[i], total_hdr_len + MSS, daddr);
 +      write_packet(fd, last, total_hdr_len + remainder, daddr);
 +      write_packet(fd, new_seg, total_hdr_len + remainder, daddr);
 +}
 +
 +/* Pure acks and dup acks don't coalesce */
 +static void send_ack(int fd, struct sockaddr_ll *daddr)
 +{
 +      static char buf[MAX_HDR_LEN];
 +
 +      create_packet(buf, 0, 0, 0, 0);
 +      write_packet(fd, buf, total_hdr_len, daddr);
 +      write_packet(fd, buf, total_hdr_len, daddr);
 +      create_packet(buf, 0, 1, 0, 0);
 +      write_packet(fd, buf, total_hdr_len, daddr);
 +}
 +
 +static void recompute_packet(char *buf, char *no_ext, int extlen)
 +{
 +      struct tcphdr *tcphdr = (struct tcphdr *)(buf + tcp_offset);
 +      struct ipv6hdr *ip6h = (struct ipv6hdr *)(buf + ETH_HLEN);
 +      struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN);
 +
 +      memmove(buf, no_ext, total_hdr_len);
 +      memmove(buf + total_hdr_len + extlen,
 +              no_ext + total_hdr_len, PAYLOAD_LEN);
 +
 +      tcphdr->doff = tcphdr->doff + (extlen / 4);
 +      tcphdr->check = 0;
 +      tcphdr->check = tcp_checksum(tcphdr, PAYLOAD_LEN + extlen);
 +      if (proto == PF_INET) {
 +              iph->tot_len = htons(ntohs(iph->tot_len) + extlen);
 +              iph->check = 0;
 +              iph->check = checksum_fold(iph, sizeof(struct iphdr), 0);
 +
 +              if (ipip) {
 +                      iph += 1;
 +                      iph->tot_len = htons(ntohs(iph->tot_len) + extlen);
 +                      iph->check = 0;
 +                      iph->check = checksum_fold(iph, sizeof(struct iphdr), 0);
 +              }
 +      } else {
 +              ip6h->payload_len = htons(ntohs(ip6h->payload_len) + extlen);
 +      }
 +}
 +
 +static void tcp_write_options(char *buf, int kind, int ts)
 +{
 +      struct tcp_option_ts {
 +              uint8_t kind;
 +              uint8_t len;
 +              uint32_t tsval;
 +              uint32_t tsecr;
 +      } *opt_ts = (void *)buf;
 +      struct tcp_option_window {
 +              uint8_t kind;
 +              uint8_t len;
 +              uint8_t shift;
 +      } *opt_window = (void *)buf;
 +
 +      switch (kind) {
 +      case TCPOPT_NOP:
 +              buf[0] = TCPOPT_NOP;
 +              break;
 +      case TCPOPT_WINDOW:
 +              memset(opt_window, 0, sizeof(struct tcp_option_window));
 +              opt_window->kind = TCPOPT_WINDOW;
 +              opt_window->len = TCPOLEN_WINDOW;
 +              opt_window->shift = 0;
 +              break;
 +      case TCPOPT_TIMESTAMP:
 +              memset(opt_ts, 0, sizeof(struct tcp_option_ts));
 +              opt_ts->kind = TCPOPT_TIMESTAMP;
 +              opt_ts->len = TCPOLEN_TIMESTAMP;
 +              opt_ts->tsval = ts;
 +              opt_ts->tsecr = 0;
 +              break;
 +      default:
 +              error(1, 0, "unimplemented TCP option");
 +              break;
 +      }
 +}
 +
 +/* TCP with options is always a permutation of {TS, NOP, NOP}.
 + * Implement different orders to verify coalescing stops.
 + */
 +static void add_standard_tcp_options(char *buf, char *no_ext, int ts, int order)
 +{
 +      switch (order) {
 +      case 0:
 +              tcp_write_options(buf + total_hdr_len, TCPOPT_NOP, 0);
 +              tcp_write_options(buf + total_hdr_len + 1, TCPOPT_NOP, 0);
 +              tcp_write_options(buf + total_hdr_len + 2 /* two NOP opts */,
 +                                TCPOPT_TIMESTAMP, ts);
 +              break;
 +      case 1:
 +              tcp_write_options(buf + total_hdr_len, TCPOPT_NOP, 0);
 +              tcp_write_options(buf + total_hdr_len + 1,
 +                                TCPOPT_TIMESTAMP, ts);
 +              tcp_write_options(buf + total_hdr_len + 1 + TCPOLEN_TIMESTAMP,
 +                                TCPOPT_NOP, 0);
 +              break;
 +      case 2:
 +              tcp_write_options(buf + total_hdr_len, TCPOPT_TIMESTAMP, ts);
 +              tcp_write_options(buf + total_hdr_len + TCPOLEN_TIMESTAMP + 1,
 +                                TCPOPT_NOP, 0);
 +              tcp_write_options(buf + total_hdr_len + TCPOLEN_TIMESTAMP + 2,
 +                                TCPOPT_NOP, 0);
 +              break;
 +      default:
 +              error(1, 0, "unknown order");
 +              break;
 +      }
 +      recompute_packet(buf, no_ext, TCPOLEN_TSTAMP_APPA);
 +}
 +
 +/* Packets with invalid checksum don't coalesce. */
 +static void send_changed_checksum(int fd, struct sockaddr_ll *daddr)
 +{
 +      static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      struct tcphdr *tcph = (struct tcphdr *)(buf + tcp_offset);
 +      int pkt_size = total_hdr_len + PAYLOAD_LEN;
 +
 +      create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
 +      write_packet(fd, buf, pkt_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
 +      tcph->check = tcph->check - 1;
 +      write_packet(fd, buf, pkt_size, daddr);
 +}
 +
 + /* Packets with non-consecutive sequence number don't coalesce.*/
 +static void send_changed_seq(int fd, struct sockaddr_ll *daddr)
 +{
 +      static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      struct tcphdr *tcph = (struct tcphdr *)(buf + tcp_offset);
 +      int pkt_size = total_hdr_len + PAYLOAD_LEN;
 +
 +      create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
 +      write_packet(fd, buf, pkt_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
 +      tcph->seq = ntohl(htonl(tcph->seq) + 1);
 +      tcph->check = 0;
 +      tcph->check = tcp_checksum(tcph, PAYLOAD_LEN);
 +      write_packet(fd, buf, pkt_size, daddr);
 +}
 +
 + /* Packet with different timestamp option or different timestamps
 +  * don't coalesce.
 +  */
 +static void send_changed_ts(int fd, struct sockaddr_ll *daddr)
 +{
 +      static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      static char extpkt[sizeof(buf) + TCPOLEN_TSTAMP_APPA];
 +      int pkt_size = total_hdr_len + PAYLOAD_LEN + TCPOLEN_TSTAMP_APPA;
 +
 +      create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
 +      add_standard_tcp_options(extpkt, buf, 0, 0);
 +      write_packet(fd, extpkt, pkt_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
 +      add_standard_tcp_options(extpkt, buf, 0, 0);
 +      write_packet(fd, extpkt, pkt_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN * 2, 0, PAYLOAD_LEN, 0);
 +      add_standard_tcp_options(extpkt, buf, 100, 0);
 +      write_packet(fd, extpkt, pkt_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN * 3, 0, PAYLOAD_LEN, 0);
 +      add_standard_tcp_options(extpkt, buf, 100, 1);
 +      write_packet(fd, extpkt, pkt_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN * 4, 0, PAYLOAD_LEN, 0);
 +      add_standard_tcp_options(extpkt, buf, 100, 2);
 +      write_packet(fd, extpkt, pkt_size, daddr);
 +}
 +
 +/* Packet with different tcp options don't coalesce. */
 +static void send_diff_opt(int fd, struct sockaddr_ll *daddr)
 +{
 +      static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      static char extpkt1[sizeof(buf) + TCPOLEN_TSTAMP_APPA];
 +      static char extpkt2[sizeof(buf) + TCPOLEN_MAXSEG];
 +      int extpkt1_size = total_hdr_len + PAYLOAD_LEN + TCPOLEN_TSTAMP_APPA;
 +      int extpkt2_size = total_hdr_len + PAYLOAD_LEN + TCPOLEN_MAXSEG;
 +
 +      create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
 +      add_standard_tcp_options(extpkt1, buf, 0, 0);
 +      write_packet(fd, extpkt1, extpkt1_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
 +      add_standard_tcp_options(extpkt1, buf, 0, 0);
 +      write_packet(fd, extpkt1, extpkt1_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN * 2, 0, PAYLOAD_LEN, 0);
 +      tcp_write_options(extpkt2 + MAX_HDR_LEN, TCPOPT_NOP, 0);
 +      tcp_write_options(extpkt2 + MAX_HDR_LEN + 1, TCPOPT_WINDOW, 0);
 +      recompute_packet(extpkt2, buf, TCPOLEN_WINDOW + 1);
 +      write_packet(fd, extpkt2, extpkt2_size, daddr);
 +}
 +
 +static void add_ipv4_ts_option(void *buf, void *optpkt)
 +{
 +      struct ip_timestamp *ts = (struct ip_timestamp *)(optpkt + tcp_offset);
 +      int optlen = sizeof(struct ip_timestamp);
 +      struct iphdr *iph;
 +
 +      if (optlen % 4)
 +              error(1, 0, "ipv4 timestamp length is not a multiple of 4B");
 +
 +      ts->ipt_code = IPOPT_TS;
 +      ts->ipt_len = optlen;
 +      ts->ipt_ptr = 5;
 +      ts->ipt_flg = IPOPT_TS_TSONLY;
 +
 +      memcpy(optpkt, buf, tcp_offset);
 +      memcpy(optpkt + tcp_offset + optlen, buf + tcp_offset,
 +             sizeof(struct tcphdr) + PAYLOAD_LEN);
 +
 +      iph = (struct iphdr *)(optpkt + ETH_HLEN);
 +      iph->ihl = 5 + (optlen / 4);
 +      iph->tot_len = htons(ntohs(iph->tot_len) + optlen);
 +      iph->check = 0;
 +      iph->check = checksum_fold(iph, sizeof(struct iphdr) + optlen, 0);
 +}
 +
 +static void add_ipv6_exthdr(void *buf, void *optpkt, __u8 exthdr_type, char *ext_payload)
 +{
 +      struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr *)(optpkt + tcp_offset);
 +      struct ipv6hdr *iph = (struct ipv6hdr *)(optpkt + ETH_HLEN);
 +      char *exthdr_payload_start = (char *)(exthdr + 1);
 +
 +      exthdr->hdrlen = 0;
 +      exthdr->nexthdr = IPPROTO_TCP;
 +
 +      memcpy(exthdr_payload_start, ext_payload, MIN_EXTHDR_SIZE - sizeof(*exthdr));
 +
 +      memcpy(optpkt, buf, tcp_offset);
 +      memcpy(optpkt + tcp_offset + MIN_EXTHDR_SIZE, buf + tcp_offset,
 +              sizeof(struct tcphdr) + PAYLOAD_LEN);
 +
 +      iph->nexthdr = exthdr_type;
 +      iph->payload_len = htons(ntohs(iph->payload_len) + MIN_EXTHDR_SIZE);
 +}
 +
 +static void fix_ip4_checksum(struct iphdr *iph)
 +{
 +      iph->check = 0;
 +      iph->check = checksum_fold(iph, sizeof(struct iphdr), 0);
 +}
 +
 +static void send_flush_id_case(int fd, struct sockaddr_ll *daddr, int tcase)
 +{
 +      static char buf1[MAX_HDR_LEN + PAYLOAD_LEN];
 +      static char buf2[MAX_HDR_LEN + PAYLOAD_LEN];
 +      static char buf3[MAX_HDR_LEN + PAYLOAD_LEN];
 +      bool send_three = false;
 +      struct iphdr *iph1;
 +      struct iphdr *iph2;
 +      struct iphdr *iph3;
 +
 +      iph1 = (struct iphdr *)(buf1 + ETH_HLEN);
 +      iph2 = (struct iphdr *)(buf2 + ETH_HLEN);
 +      iph3 = (struct iphdr *)(buf3 + ETH_HLEN);
 +
 +      create_packet(buf1, 0, 0, PAYLOAD_LEN, 0);
 +      create_packet(buf2, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
 +      create_packet(buf3, PAYLOAD_LEN * 2, 0, PAYLOAD_LEN, 0);
 +
 +      switch (tcase) {
 +      case 0: /* DF=1, Incrementing - should coalesce */
 +              iph1->frag_off |= htons(IP_DF);
 +              iph1->id = htons(8);
 +
 +              iph2->frag_off |= htons(IP_DF);
 +              iph2->id = htons(9);
 +              break;
 +
 +      case 1: /* DF=1, Fixed - should coalesce */
 +              iph1->frag_off |= htons(IP_DF);
 +              iph1->id = htons(8);
 +
 +              iph2->frag_off |= htons(IP_DF);
 +              iph2->id = htons(8);
 +              break;
 +
 +      case 2: /* DF=0, Incrementing - should coalesce */
 +              iph1->frag_off &= ~htons(IP_DF);
 +              iph1->id = htons(8);
 +
 +              iph2->frag_off &= ~htons(IP_DF);
 +              iph2->id = htons(9);
 +              break;
 +
 +      case 3: /* DF=0, Fixed - should coalesce */
 +              iph1->frag_off &= ~htons(IP_DF);
 +              iph1->id = htons(8);
 +
 +              iph2->frag_off &= ~htons(IP_DF);
 +              iph2->id = htons(8);
 +              break;
 +
 +      case 4: /* DF=1, two packets incrementing, and one fixed - should
 +               * coalesce only the first two packets
 +               */
 +              iph1->frag_off |= htons(IP_DF);
 +              iph1->id = htons(8);
 +
 +              iph2->frag_off |= htons(IP_DF);
 +              iph2->id = htons(9);
 +
 +              iph3->frag_off |= htons(IP_DF);
 +              iph3->id = htons(9);
 +              send_three = true;
 +              break;
 +
 +      case 5: /* DF=1, two packets fixed, and one incrementing - should
 +               * coalesce only the first two packets
 +               */
 +              iph1->frag_off |= htons(IP_DF);
 +              iph1->id = htons(8);
 +
 +              iph2->frag_off |= htons(IP_DF);
 +              iph2->id = htons(8);
 +
 +              iph3->frag_off |= htons(IP_DF);
 +              iph3->id = htons(9);
 +              send_three = true;
 +              break;
 +      }
 +
 +      fix_ip4_checksum(iph1);
 +      fix_ip4_checksum(iph2);
 +      write_packet(fd, buf1, total_hdr_len + PAYLOAD_LEN, daddr);
 +      write_packet(fd, buf2, total_hdr_len + PAYLOAD_LEN, daddr);
 +
 +      if (send_three) {
 +              fix_ip4_checksum(iph3);
 +              write_packet(fd, buf3, total_hdr_len + PAYLOAD_LEN, daddr);
 +      }
 +}
 +
 +static void test_flush_id(int fd, struct sockaddr_ll *daddr, char *fin_pkt)
 +{
 +      for (int i = 0; i < num_flush_id_cases; i++) {
 +              sleep(1);
 +              send_flush_id_case(fd, daddr, i);
 +              sleep(1);
 +              write_packet(fd, fin_pkt, total_hdr_len, daddr);
 +      }
 +}
 +
 +static void send_ipv6_exthdr(int fd, struct sockaddr_ll *daddr, char *ext_data1, char *ext_data2)
 +{
 +      static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      static char exthdr_pck[sizeof(buf) + MIN_EXTHDR_SIZE];
 +
 +      create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
 +      add_ipv6_exthdr(buf, exthdr_pck, IPPROTO_DSTOPTS, ext_data1);
 +      write_packet(fd, exthdr_pck, total_hdr_len + PAYLOAD_LEN + MIN_EXTHDR_SIZE, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN * 1, 0, PAYLOAD_LEN, 0);
 +      add_ipv6_exthdr(buf, exthdr_pck, IPPROTO_DSTOPTS, ext_data2);
 +      write_packet(fd, exthdr_pck, total_hdr_len + PAYLOAD_LEN + MIN_EXTHDR_SIZE, daddr);
 +}
 +
 +/* IPv4 options shouldn't coalesce */
 +static void send_ip_options(int fd, struct sockaddr_ll *daddr)
 +{
 +      static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      static char optpkt[sizeof(buf) + sizeof(struct ip_timestamp)];
 +      int optlen = sizeof(struct ip_timestamp);
 +      int pkt_size = total_hdr_len + PAYLOAD_LEN + optlen;
 +
 +      create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
 +      write_packet(fd, buf, total_hdr_len + PAYLOAD_LEN, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN * 1, 0, PAYLOAD_LEN, 0);
 +      add_ipv4_ts_option(buf, optpkt);
 +      write_packet(fd, optpkt, pkt_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN * 2, 0, PAYLOAD_LEN, 0);
 +      write_packet(fd, buf, total_hdr_len + PAYLOAD_LEN, daddr);
 +}
 +
 +/*  IPv4 fragments shouldn't coalesce */
 +static void send_fragment4(int fd, struct sockaddr_ll *daddr)
 +{
 +      static char buf[IP_MAXPACKET];
 +      struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN);
 +      int pkt_size = total_hdr_len + PAYLOAD_LEN;
 +
 +      create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
 +      write_packet(fd, buf, pkt_size, daddr);
 +
 +      /* Once fragmented, packet would retain the total_len.
 +       * Tcp header is prepared as if rest of data is in follow-up frags,
 +       * but follow up frags aren't actually sent.
 +       */
 +      memset(buf + total_hdr_len, 'a', PAYLOAD_LEN * 2);
 +      fill_transportlayer(buf + tcp_offset, PAYLOAD_LEN, 0, PAYLOAD_LEN * 2, 0);
 +      fill_networklayer(buf + ETH_HLEN, PAYLOAD_LEN, IPPROTO_TCP);
 +      fill_datalinklayer(buf);
 +
 +      iph->frag_off = htons(0x6000); // DF = 1, MF = 1
 +      iph->check = 0;
 +      iph->check = checksum_fold(iph, sizeof(struct iphdr), 0);
 +      write_packet(fd, buf, pkt_size, daddr);
 +}
 +
 +/* IPv4 packets with different ttl don't coalesce.*/
 +static void send_changed_ttl(int fd, struct sockaddr_ll *daddr)
 +{
 +      int pkt_size = total_hdr_len + PAYLOAD_LEN;
 +      static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN);
 +
 +      create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
 +      write_packet(fd, buf, pkt_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
 +      iph->ttl = 7;
 +      iph->check = 0;
 +      iph->check = checksum_fold(iph, sizeof(struct iphdr), 0);
 +      write_packet(fd, buf, pkt_size, daddr);
 +}
 +
 +/* Packets with different tos don't coalesce.*/
 +static void send_changed_tos(int fd, struct sockaddr_ll *daddr)
 +{
 +      int pkt_size = total_hdr_len + PAYLOAD_LEN;
 +      static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN);
 +      struct ipv6hdr *ip6h = (struct ipv6hdr *)(buf + ETH_HLEN);
 +
 +      create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
 +      write_packet(fd, buf, pkt_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
 +      if (proto == PF_INET) {
 +              iph->tos = 1;
 +              iph->check = 0;
 +              iph->check = checksum_fold(iph, sizeof(struct iphdr), 0);
 +      } else if (proto == PF_INET6) {
 +              ip6h->priority = 0xf;
 +      }
 +      write_packet(fd, buf, pkt_size, daddr);
 +}
 +
 +/* Packets with different ECN don't coalesce.*/
 +static void send_changed_ECN(int fd, struct sockaddr_ll *daddr)
 +{
 +      int pkt_size = total_hdr_len + PAYLOAD_LEN;
 +      static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN);
 +
 +      create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
 +      write_packet(fd, buf, pkt_size, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
 +      if (proto == PF_INET) {
 +              buf[ETH_HLEN + 1] ^= 0x2; // ECN set to 10
 +              iph->check = 0;
 +              iph->check = checksum_fold(iph, sizeof(struct iphdr), 0);
 +      } else {
 +              buf[ETH_HLEN + 1] ^= 0x20; // ECN set to 10
 +      }
 +      write_packet(fd, buf, pkt_size, daddr);
 +}
 +
 +/* IPv6 fragments and packets with extensions don't coalesce.*/
 +static void send_fragment6(int fd, struct sockaddr_ll *daddr)
 +{
 +      static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
 +      static char extpkt[MAX_HDR_LEN + PAYLOAD_LEN +
 +                         sizeof(struct ip6_frag)];
 +      struct ipv6hdr *ip6h = (struct ipv6hdr *)(buf + ETH_HLEN);
 +      struct ip6_frag *frag = (void *)(extpkt + tcp_offset);
 +      int extlen = sizeof(struct ip6_frag);
 +      int bufpkt_len = total_hdr_len + PAYLOAD_LEN;
 +      int extpkt_len = bufpkt_len + extlen;
 +      int i;
 +
 +      for (i = 0; i < 2; i++) {
 +              create_packet(buf, PAYLOAD_LEN * i, 0, PAYLOAD_LEN, 0);
 +              write_packet(fd, buf, bufpkt_len, daddr);
 +      }
 +      sleep(1);
 +      create_packet(buf, PAYLOAD_LEN * 2, 0, PAYLOAD_LEN, 0);
 +      memset(extpkt, 0, extpkt_len);
 +
 +      ip6h->nexthdr = IPPROTO_FRAGMENT;
 +      ip6h->payload_len = htons(ntohs(ip6h->payload_len) + extlen);
 +      frag->ip6f_nxt = IPPROTO_TCP;
 +
 +      memcpy(extpkt, buf, tcp_offset);
 +      memcpy(extpkt + tcp_offset + extlen, buf + tcp_offset,
 +             sizeof(struct tcphdr) + PAYLOAD_LEN);
 +      write_packet(fd, extpkt, extpkt_len, daddr);
 +
 +      create_packet(buf, PAYLOAD_LEN * 3, 0, PAYLOAD_LEN, 0);
 +      write_packet(fd, buf, bufpkt_len, daddr);
 +}
 +
 +static void bind_packetsocket(int fd)
 +{
 +      struct sockaddr_ll daddr = {};
 +
 +      daddr.sll_family = AF_PACKET;
 +      daddr.sll_protocol = ethhdr_proto;
 +      daddr.sll_ifindex = if_nametoindex(ifname);
 +      if (daddr.sll_ifindex == 0)
 +              error(1, errno, "if_nametoindex");
 +
 +      if (bind(fd, (void *)&daddr, sizeof(daddr)) < 0)
 +              error(1, errno, "could not bind socket");
 +}
 +
 +static void set_timeout(int fd)
 +{
 +      struct timeval timeout;
 +
 +      timeout.tv_sec = 3;
 +      timeout.tv_usec = 0;
 +      if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
 +                     sizeof(timeout)) < 0)
 +              error(1, errno, "cannot set timeout, setsockopt failed");
 +}
 +
 +static void check_recv_pkts(int fd, int *correct_payload,
 +                          int correct_num_pkts)
 +{
 +      static char buffer[IP_MAXPACKET + ETH_HLEN + 1];
 +      struct iphdr *iph = (struct iphdr *)(buffer + ETH_HLEN);
 +      struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + ETH_HLEN);
 +      struct tcphdr *tcph;
 +      bool bad_packet = false;
 +      int tcp_ext_len = 0;
 +      int ip_ext_len = 0;
 +      int pkt_size = -1;
 +      int data_len = 0;
 +      int num_pkt = 0;
 +      int i;
 +
 +      vlog("Expected {");
 +      for (i = 0; i < correct_num_pkts; i++)
 +              vlog("%d ", correct_payload[i]);
 +      vlog("}, Total %d packets\nReceived {", correct_num_pkts);
 +
 +      while (1) {
 +              ip_ext_len = 0;
 +              pkt_size = recv(fd, buffer, IP_MAXPACKET + ETH_HLEN + 1, 0);
 +              if (pkt_size < 0)
 +                      error(1, errno, "could not receive");
 +
 +              if (iph->version == 4)
 +                      ip_ext_len = (iph->ihl - 5) * 4;
 +              else if (ip6h->version == 6 && ip6h->nexthdr != IPPROTO_TCP)
 +                      ip_ext_len = MIN_EXTHDR_SIZE;
 +
 +              tcph = (struct tcphdr *)(buffer + tcp_offset + ip_ext_len);
 +
 +              if (tcph->fin)
 +                      break;
 +
 +              tcp_ext_len = (tcph->doff - 5) * 4;
 +              data_len = pkt_size - total_hdr_len - tcp_ext_len - ip_ext_len;
 +              /* Min ethernet frame payload is 46(ETH_ZLEN - ETH_HLEN) by RFC 802.3.
 +               * Ipv4/tcp packets without at least 6 bytes of data will be padded.
 +               * Packet sockets are protocol agnostic, and will not trim the padding.
 +               */
 +              if (pkt_size == ETH_ZLEN && iph->version == 4) {
 +                      data_len = ntohs(iph->tot_len)
 +                              - sizeof(struct tcphdr) - sizeof(struct iphdr);
 +              }
 +              vlog("%d ", data_len);
 +              if (data_len != correct_payload[num_pkt]) {
 +                      vlog("[!=%d]", correct_payload[num_pkt]);
 +                      bad_packet = true;
 +              }
 +              num_pkt++;
 +      }
 +      vlog("}, Total %d packets.\n", num_pkt);
 +      if (num_pkt != correct_num_pkts)
 +              error(1, 0, "incorrect number of packets");
 +      if (bad_packet)
 +              error(1, 0, "incorrect packet geometry");
 +
 +      printf("Test succeeded\n\n");
 +}
 +
 +static void gro_sender(void)
 +{
 +      const int fin_delay_us = 100 * 1000;
 +      static char fin_pkt[MAX_HDR_LEN];
 +      struct sockaddr_ll daddr = {};
 +      int txfd = -1;
 +
 +      txfd = socket(PF_PACKET, SOCK_RAW, IPPROTO_RAW);
 +      if (txfd < 0)
 +              error(1, errno, "socket creation");
 +
 +      memset(&daddr, 0, sizeof(daddr));
 +      daddr.sll_ifindex = if_nametoindex(ifname);
 +      if (daddr.sll_ifindex == 0)
 +              error(1, errno, "if_nametoindex");
 +      daddr.sll_family = AF_PACKET;
 +      memcpy(daddr.sll_addr, dst_mac, ETH_ALEN);
 +      daddr.sll_halen = ETH_ALEN;
 +      create_packet(fin_pkt, PAYLOAD_LEN * 2, 0, 0, 1);
 +
 +      if (strcmp(testname, "data") == 0) {
 +              send_data_pkts(txfd, &daddr, PAYLOAD_LEN, PAYLOAD_LEN);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +              send_data_pkts(txfd, &daddr, PAYLOAD_LEN, PAYLOAD_LEN / 2);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +              send_data_pkts(txfd, &daddr, PAYLOAD_LEN / 2, PAYLOAD_LEN);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +      } else if (strcmp(testname, "ack") == 0) {
 +              send_ack(txfd, &daddr);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +      } else if (strcmp(testname, "flags") == 0) {
 +              send_flags(txfd, &daddr, 1, 0, 0, 0);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +              send_flags(txfd, &daddr, 0, 1, 0, 0);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +              send_flags(txfd, &daddr, 0, 0, 1, 0);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +              send_flags(txfd, &daddr, 0, 0, 0, 1);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +      } else if (strcmp(testname, "tcp") == 0) {
 +              send_changed_checksum(txfd, &daddr);
 +              /* Adding sleep before sending FIN so that it is not
 +               * received prior to other packets.
 +               */
 +              usleep(fin_delay_us);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +              send_changed_seq(txfd, &daddr);
 +              usleep(fin_delay_us);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +              send_changed_ts(txfd, &daddr);
 +              usleep(fin_delay_us);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +              send_diff_opt(txfd, &daddr);
 +              usleep(fin_delay_us);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +      } else if (strcmp(testname, "ip") == 0) {
 +              send_changed_ECN(txfd, &daddr);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +              send_changed_tos(txfd, &daddr);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +              if (proto == PF_INET) {
 +                      /* Modified packets may be received out of order.
 +                       * Sleep function added to enforce test boundaries
 +                       * so that fin pkts are not received prior to other pkts.
 +                       */
 +                      sleep(1);
 +                      send_changed_ttl(txfd, &daddr);
 +                      write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +                      sleep(1);
 +                      send_ip_options(txfd, &daddr);
 +                      sleep(1);
 +                      write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +                      sleep(1);
 +                      send_fragment4(txfd, &daddr);
 +                      sleep(1);
 +                      write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +                      test_flush_id(txfd, &daddr, fin_pkt);
 +              } else if (proto == PF_INET6) {
 +                      sleep(1);
 +                      send_fragment6(txfd, &daddr);
 +                      sleep(1);
 +                      write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +                      sleep(1);
 +                      /* send IPv6 packets with ext header with same payload */
 +                      send_ipv6_exthdr(txfd, &daddr, EXT_PAYLOAD_1, EXT_PAYLOAD_1);
 +                      sleep(1);
 +                      write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +                      sleep(1);
 +                      /* send IPv6 packets with ext header with different payload */
 +                      send_ipv6_exthdr(txfd, &daddr, EXT_PAYLOAD_1, EXT_PAYLOAD_2);
 +                      sleep(1);
 +                      write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +              }
 +      } else if (strcmp(testname, "large") == 0) {
 +              /* 20 is the difference between min iphdr size
 +               * and min ipv6hdr size. Like MAX_HDR_SIZE,
 +               * MAX_PAYLOAD is defined with the larger header of the two.
 +               */
 +              int offset = (proto == PF_INET && !ipip) ? 20 : 0;
 +              int remainder = (MAX_PAYLOAD + offset) % MSS;
 +
 +              send_large(txfd, &daddr, remainder);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +
 +              send_large(txfd, &daddr, remainder + 1);
 +              write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 +      } else {
 +              error(1, 0, "Unknown testcase");
 +      }
 +
 +      if (close(txfd))
 +              error(1, errno, "socket close");
 +}
 +
 +static void gro_receiver(void)
 +{
 +      static int correct_payload[NUM_PACKETS];
 +      int rxfd = -1;
 +
 +      rxfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_NONE));
 +      if (rxfd < 0)
 +              error(1, 0, "socket creation");
 +      setup_sock_filter(rxfd);
 +      set_timeout(rxfd);
 +      bind_packetsocket(rxfd);
 +
 +      ksft_ready();
 +
 +      memset(correct_payload, 0, sizeof(correct_payload));
 +
 +      if (strcmp(testname, "data") == 0) {
 +              printf("pure data packet of same size: ");
 +              correct_payload[0] = PAYLOAD_LEN * 2;
 +              check_recv_pkts(rxfd, correct_payload, 1);
 +
 +              printf("large data packets followed by a smaller one: ");
 +              correct_payload[0] = PAYLOAD_LEN * 1.5;
 +              check_recv_pkts(rxfd, correct_payload, 1);
 +
 +              printf("small data packets followed by a larger one: ");
 +              correct_payload[0] = PAYLOAD_LEN / 2;
 +              correct_payload[1] = PAYLOAD_LEN;
 +              check_recv_pkts(rxfd, correct_payload, 2);
 +      } else if (strcmp(testname, "ack") == 0) {
 +              printf("duplicate ack and pure ack: ");
 +              check_recv_pkts(rxfd, correct_payload, 3);
 +      } else if (strcmp(testname, "flags") == 0) {
 +              correct_payload[0] = PAYLOAD_LEN * 3;
 +              correct_payload[1] = PAYLOAD_LEN * 2;
 +
 +              printf("psh flag ends coalescing: ");
 +              check_recv_pkts(rxfd, correct_payload, 2);
 +
 +              correct_payload[0] = PAYLOAD_LEN * 2;
 +              correct_payload[1] = 0;
 +              correct_payload[2] = PAYLOAD_LEN * 2;
 +              printf("syn flag ends coalescing: ");
 +              check_recv_pkts(rxfd, correct_payload, 3);
 +
 +              printf("rst flag ends coalescing: ");
 +              check_recv_pkts(rxfd, correct_payload, 3);
 +
 +              printf("urg flag ends coalescing: ");
 +              check_recv_pkts(rxfd, correct_payload, 3);
 +      } else if (strcmp(testname, "tcp") == 0) {
 +              correct_payload[0] = PAYLOAD_LEN;
 +              correct_payload[1] = PAYLOAD_LEN;
 +              correct_payload[2] = PAYLOAD_LEN;
 +              correct_payload[3] = PAYLOAD_LEN;
 +
 +              printf("changed checksum does not coalesce: ");
 +              check_recv_pkts(rxfd, correct_payload, 2);
 +
 +              printf("Wrong Seq number doesn't coalesce: ");
 +              check_recv_pkts(rxfd, correct_payload, 2);
 +
 +              printf("Different timestamp doesn't coalesce: ");
 +              correct_payload[0] = PAYLOAD_LEN * 2;
 +              check_recv_pkts(rxfd, correct_payload, 4);
 +
 +              printf("Different options doesn't coalesce: ");
 +              correct_payload[0] = PAYLOAD_LEN * 2;
 +              check_recv_pkts(rxfd, correct_payload, 2);
 +      } else if (strcmp(testname, "ip") == 0) {
 +              correct_payload[0] = PAYLOAD_LEN;
 +              correct_payload[1] = PAYLOAD_LEN;
 +
 +              printf("different ECN doesn't coalesce: ");
 +              check_recv_pkts(rxfd, correct_payload, 2);
 +
 +              printf("different tos doesn't coalesce: ");
 +              check_recv_pkts(rxfd, correct_payload, 2);
 +
 +              if (proto == PF_INET) {
 +                      printf("different ttl doesn't coalesce: ");
 +                      check_recv_pkts(rxfd, correct_payload, 2);
 +
 +                      printf("ip options doesn't coalesce: ");
 +                      correct_payload[2] = PAYLOAD_LEN;
 +                      check_recv_pkts(rxfd, correct_payload, 3);
 +
 +                      printf("fragmented ip4 doesn't coalesce: ");
 +                      check_recv_pkts(rxfd, correct_payload, 2);
 +
 +                      /* is_atomic checks */
 +                      printf("DF=1, Incrementing - should coalesce: ");
 +                      correct_payload[0] = PAYLOAD_LEN * 2;
 +                      check_recv_pkts(rxfd, correct_payload, 1);
 +
 +                      printf("DF=1, Fixed - should coalesce: ");
 +                      correct_payload[0] = PAYLOAD_LEN * 2;
 +                      check_recv_pkts(rxfd, correct_payload, 1);
 +
 +                      printf("DF=0, Incrementing - should coalesce: ");
 +                      correct_payload[0] = PAYLOAD_LEN * 2;
 +                      check_recv_pkts(rxfd, correct_payload, 1);
 +
 +                      printf("DF=0, Fixed - should coalesce: ");
 +                      correct_payload[0] = PAYLOAD_LEN * 2;
 +                      check_recv_pkts(rxfd, correct_payload, 1);
 +
 +                      printf("DF=1, 2 Incrementing and one fixed - should coalesce only first 2 packets: ");
 +                      correct_payload[0] = PAYLOAD_LEN * 2;
 +                      correct_payload[1] = PAYLOAD_LEN;
 +                      check_recv_pkts(rxfd, correct_payload, 2);
 +
 +                      printf("DF=1, 2 Fixed and one incrementing - should coalesce only first 2 packets: ");
 +                      correct_payload[0] = PAYLOAD_LEN * 2;
 +                      correct_payload[1] = PAYLOAD_LEN;
 +                      check_recv_pkts(rxfd, correct_payload, 2);
 +              } else if (proto == PF_INET6) {
 +                      /* GRO doesn't check for ipv6 hop limit when flushing.
 +                       * Hence no corresponding test to the ipv4 case.
 +                       */
 +                      printf("fragmented ip6 doesn't coalesce: ");
 +                      correct_payload[0] = PAYLOAD_LEN * 2;
 +                      correct_payload[1] = PAYLOAD_LEN;
 +                      correct_payload[2] = PAYLOAD_LEN;
 +                      check_recv_pkts(rxfd, correct_payload, 3);
 +
 +                      printf("ipv6 with ext header does coalesce: ");
 +                      correct_payload[0] = PAYLOAD_LEN * 2;
 +                      check_recv_pkts(rxfd, correct_payload, 1);
 +
 +                      printf("ipv6 with ext header with different payloads doesn't coalesce: ");
 +                      correct_payload[0] = PAYLOAD_LEN;
 +                      correct_payload[1] = PAYLOAD_LEN;
 +                      check_recv_pkts(rxfd, correct_payload, 2);
 +              }
 +      } else if (strcmp(testname, "large") == 0) {
 +              int offset = (proto == PF_INET && !ipip) ? 20 : 0;
 +              int remainder = (MAX_PAYLOAD + offset) % MSS;
 +
 +              correct_payload[0] = (MAX_PAYLOAD + offset);
 +              correct_payload[1] = remainder;
 +              printf("Shouldn't coalesce if exceed IP max pkt size: ");
 +              check_recv_pkts(rxfd, correct_payload, 2);
 +
 +              /* last segment sent individually, doesn't start new segment */
 +              correct_payload[0] = correct_payload[0] - remainder;
 +              correct_payload[1] = remainder + 1;
 +              correct_payload[2] = remainder + 1;
 +              check_recv_pkts(rxfd, correct_payload, 3);
 +      } else {
 +              error(1, 0, "Test case error, should never trigger");
 +      }
 +
 +      if (close(rxfd))
 +              error(1, 0, "socket close");
 +}
 +
 +static void parse_args(int argc, char **argv)
 +{
 +      static const struct option opts[] = {
 +              { "daddr", required_argument, NULL, 'd' },
 +              { "dmac", required_argument, NULL, 'D' },
 +              { "iface", required_argument, NULL, 'i' },
 +              { "ipv4", no_argument, NULL, '4' },
 +              { "ipv6", no_argument, NULL, '6' },
 +              { "ipip", no_argument, NULL, 'e' },
 +              { "rx", no_argument, NULL, 'r' },
 +              { "saddr", required_argument, NULL, 's' },
 +              { "smac", required_argument, NULL, 'S' },
 +              { "test", required_argument, NULL, 't' },
 +              { "verbose", no_argument, NULL, 'v' },
 +              { 0, 0, 0, 0 }
 +      };
 +      int c;
 +
 +      while ((c = getopt_long(argc, argv, "46d:D:ei:rs:S:t:v", opts, NULL)) != -1) {
 +              switch (c) {
 +              case '4':
 +                      proto = PF_INET;
 +                      ethhdr_proto = htons(ETH_P_IP);
 +                      break;
 +              case '6':
 +                      proto = PF_INET6;
 +                      ethhdr_proto = htons(ETH_P_IPV6);
 +                      break;
 +              case 'e':
 +                      ipip = true;
 +                      proto = PF_INET;
 +                      ethhdr_proto = htons(ETH_P_IP);
 +                      break;
 +              case 'd':
 +                      addr4_dst = addr6_dst = optarg;
 +                      break;
 +              case 'D':
 +                      dmac = optarg;
 +                      break;
 +              case 'i':
 +                      ifname = optarg;
 +                      break;
 +              case 'r':
 +                      tx_socket = false;
 +                      break;
 +              case 's':
 +                      addr4_src = addr6_src = optarg;
 +                      break;
 +              case 'S':
 +                      smac = optarg;
 +                      break;
 +              case 't':
 +                      testname = optarg;
 +                      break;
 +              case 'v':
 +                      verbose = true;
 +                      break;
 +              default:
 +                      error(1, 0, "%s invalid option %c\n", __func__, c);
 +                      break;
 +              }
 +      }
 +}
 +
 +int main(int argc, char **argv)
 +{
 +      parse_args(argc, argv);
 +
 +      if (ipip) {
 +              tcp_offset = ETH_HLEN + sizeof(struct iphdr) * 2;
 +              total_hdr_len = tcp_offset + sizeof(struct tcphdr);
 +      } else if (proto == PF_INET) {
 +              tcp_offset = ETH_HLEN + sizeof(struct iphdr);
 +              total_hdr_len = tcp_offset + sizeof(struct tcphdr);
 +      } else if (proto == PF_INET6) {
 +              tcp_offset = ETH_HLEN + sizeof(struct ipv6hdr);
 +              total_hdr_len = MAX_HDR_LEN;
 +      } else {
 +              error(1, 0, "Protocol family is not ipv4 or ipv6");
 +      }
 +
 +      read_MAC(src_mac, smac);
 +      read_MAC(dst_mac, dmac);
 +
 +      if (tx_socket) {
 +              gro_sender();
 +      } else {
 +              /* Only the receiver exit status determines test success. */
 +              gro_receiver();
 +              fprintf(stderr, "Gro::%s test passed.\n", testname);
 +      }
 +
 +      return 0;
 +}
index a4d0443,0000000..d23b3b0
mode 100644,000000..100644
--- /dev/null
@@@ -1,655 -1,0 +1,655 @@@
- #include "../../../kselftest.h"
 +// SPDX-License-Identifier: GPL-2.0
 +/* Toeplitz test
 + *
 + * 1. Read packets and their rx_hash using PF_PACKET/TPACKET_V3
 + * 2. Compute the rx_hash in software based on the packet contents
 + * 3. Compare the two
 + *
 + * Optionally, either '-C $rx_irq_cpu_list' or '-r $rps_bitmap' may be given.
 + *
 + * If '-C $rx_irq_cpu_list' is given, also
 + *
 + * 4. Identify the cpu on which the packet arrived with PACKET_FANOUT_CPU
 + * 5. Compute the rxqueue that RSS would select based on this rx_hash
 + * 6. Using the $rx_irq_cpu_list map, identify the arriving cpu based on rxq irq
 + * 7. Compare the cpus from 4 and 6
 + *
 + * Else if '-r $rps_bitmap' is given, also
 + *
 + * 4. Identify the cpu on which the packet arrived with PACKET_FANOUT_CPU
 + * 5. Compute the cpu that RPS should select based on rx_hash and $rps_bitmap
 + * 6. Compare the cpus from 4 and 5
 + */
 +
 +#define _GNU_SOURCE
 +
 +#include <arpa/inet.h>
 +#include <errno.h>
 +#include <error.h>
 +#include <fcntl.h>
 +#include <getopt.h>
 +#include <linux/filter.h>
 +#include <linux/if_ether.h>
 +#include <linux/if_packet.h>
 +#include <net/if.h>
 +#include <netdb.h>
 +#include <netinet/ip.h>
 +#include <netinet/ip6.h>
 +#include <netinet/tcp.h>
 +#include <netinet/udp.h>
 +#include <poll.h>
 +#include <stdbool.h>
 +#include <stddef.h>
 +#include <stdint.h>
 +#include <stdio.h>
 +#include <stdlib.h>
 +#include <string.h>
 +#include <sys/mman.h>
 +#include <sys/socket.h>
 +#include <sys/stat.h>
 +#include <sys/sysinfo.h>
 +#include <sys/time.h>
 +#include <sys/types.h>
 +#include <unistd.h>
 +
 +#include <ynl.h>
 +#include "ethtool-user.h"
 +
++#include "kselftest.h"
 +#include "../../../net/lib/ksft.h"
 +
 +#define TOEPLITZ_KEY_MIN_LEN  40
 +#define TOEPLITZ_KEY_MAX_LEN  60
 +
 +#define TOEPLITZ_STR_LEN(K)   (((K) * 3) - 1) /* hex encoded: AA:BB:CC:...:ZZ */
 +#define TOEPLITZ_STR_MIN_LEN  TOEPLITZ_STR_LEN(TOEPLITZ_KEY_MIN_LEN)
 +#define TOEPLITZ_STR_MAX_LEN  TOEPLITZ_STR_LEN(TOEPLITZ_KEY_MAX_LEN)
 +
 +#define FOUR_TUPLE_MAX_LEN    ((sizeof(struct in6_addr) * 2) + (sizeof(uint16_t) * 2))
 +
 +#define RSS_MAX_CPUS (1 << 16)        /* real constraint is PACKET_FANOUT_MAX */
 +#define RSS_MAX_INDIR (1 << 16)
 +
 +#define RPS_MAX_CPUS 16UL     /* must be a power of 2 */
 +
 +/* configuration options (cmdline arguments) */
 +static uint16_t cfg_dport =   8000;
 +static int cfg_family =               AF_INET6;
 +static char *cfg_ifname =     "eth0";
 +static int cfg_num_queues;
 +static int cfg_num_rps_cpus;
 +static bool cfg_sink;
 +static int cfg_type =         SOCK_STREAM;
 +static int cfg_timeout_msec = 1000;
 +static bool cfg_verbose;
 +
 +/* global vars */
 +static int num_cpus;
 +static int ring_block_nr;
 +static int ring_block_sz;
 +
 +/* stats */
 +static int frames_received;
 +static int frames_nohash;
 +static int frames_error;
 +
 +#define log_verbose(args...)  do { if (cfg_verbose) fprintf(stderr, args); } while (0)
 +
 +/* tpacket ring */
 +struct ring_state {
 +      int fd;
 +      char *mmap;
 +      int idx;
 +      int cpu;
 +};
 +
 +static unsigned int rx_irq_cpus[RSS_MAX_CPUS];        /* map from rxq to cpu */
 +static int rps_silo_to_cpu[RPS_MAX_CPUS];
 +static unsigned char toeplitz_key[TOEPLITZ_KEY_MAX_LEN];
 +static unsigned int rss_indir_tbl[RSS_MAX_INDIR];
 +static unsigned int rss_indir_tbl_size;
 +static struct ring_state rings[RSS_MAX_CPUS];
 +
 +static inline uint32_t toeplitz(const unsigned char *four_tuple,
 +                              const unsigned char *key)
 +{
 +      int i, bit, ret = 0;
 +      uint32_t key32;
 +
 +      key32 = ntohl(*((uint32_t *)key));
 +      key += 4;
 +
 +      for (i = 0; i < FOUR_TUPLE_MAX_LEN; i++) {
 +              for (bit = 7; bit >= 0; bit--) {
 +                      if (four_tuple[i] & (1 << bit))
 +                              ret ^= key32;
 +
 +                      key32 <<= 1;
 +                      key32 |= !!(key[0] & (1 << bit));
 +              }
 +              key++;
 +      }
 +
 +      return ret;
 +}
 +
 +/* Compare computed cpu with arrival cpu from packet_fanout_cpu */
 +static void verify_rss(uint32_t rx_hash, int cpu)
 +{
 +      int queue;
 +
 +      if (rss_indir_tbl_size)
 +              queue = rss_indir_tbl[rx_hash % rss_indir_tbl_size];
 +      else
 +              queue = rx_hash % cfg_num_queues;
 +
 +      log_verbose(" rxq %d (cpu %d)", queue, rx_irq_cpus[queue]);
 +      if (rx_irq_cpus[queue] != cpu) {
 +              log_verbose(". error: rss cpu mismatch (%d)", cpu);
 +              frames_error++;
 +      }
 +}
 +
 +static void verify_rps(uint64_t rx_hash, int cpu)
 +{
 +      int silo = (rx_hash * cfg_num_rps_cpus) >> 32;
 +
 +      log_verbose(" silo %d (cpu %d)", silo, rps_silo_to_cpu[silo]);
 +      if (rps_silo_to_cpu[silo] != cpu) {
 +              log_verbose(". error: rps cpu mismatch (%d)", cpu);
 +              frames_error++;
 +      }
 +}
 +
 +static void log_rxhash(int cpu, uint32_t rx_hash,
 +                     const char *addrs, int addr_len)
 +{
 +      char saddr[INET6_ADDRSTRLEN], daddr[INET6_ADDRSTRLEN];
 +      uint16_t *ports;
 +
 +      if (!inet_ntop(cfg_family, addrs, saddr, sizeof(saddr)) ||
 +          !inet_ntop(cfg_family, addrs + addr_len, daddr, sizeof(daddr)))
 +              error(1, 0, "address parse error");
 +
 +      ports = (void *)addrs + (addr_len * 2);
 +      log_verbose("cpu %d: rx_hash 0x%08x [saddr %s daddr %s sport %02hu dport %02hu]",
 +                  cpu, rx_hash, saddr, daddr,
 +                  ntohs(ports[0]), ntohs(ports[1]));
 +}
 +
 +/* Compare computed rxhash with rxhash received from tpacket_v3 */
 +static void verify_rxhash(const char *pkt, uint32_t rx_hash, int cpu)
 +{
 +      unsigned char four_tuple[FOUR_TUPLE_MAX_LEN] = {0};
 +      uint32_t rx_hash_sw;
 +      const char *addrs;
 +      int addr_len;
 +
 +      if (cfg_family == AF_INET) {
 +              addr_len = sizeof(struct in_addr);
 +              addrs = pkt + offsetof(struct iphdr, saddr);
 +      } else {
 +              addr_len = sizeof(struct in6_addr);
 +              addrs = pkt + offsetof(struct ip6_hdr, ip6_src);
 +      }
 +
 +      memcpy(four_tuple, addrs, (addr_len * 2) + (sizeof(uint16_t) * 2));
 +      rx_hash_sw = toeplitz(four_tuple, toeplitz_key);
 +
 +      if (cfg_verbose)
 +              log_rxhash(cpu, rx_hash, addrs, addr_len);
 +
 +      if (rx_hash != rx_hash_sw) {
 +              log_verbose(" != expected 0x%x\n", rx_hash_sw);
 +              frames_error++;
 +              return;
 +      }
 +
 +      log_verbose(" OK");
 +      if (cfg_num_queues)
 +              verify_rss(rx_hash, cpu);
 +      else if (cfg_num_rps_cpus)
 +              verify_rps(rx_hash, cpu);
 +      log_verbose("\n");
 +}
 +
 +static char *recv_frame(const struct ring_state *ring, char *frame)
 +{
 +      struct tpacket3_hdr *hdr = (void *)frame;
 +
 +      if (hdr->hv1.tp_rxhash)
 +              verify_rxhash(frame + hdr->tp_net, hdr->hv1.tp_rxhash,
 +                            ring->cpu);
 +      else
 +              frames_nohash++;
 +
 +      return frame + hdr->tp_next_offset;
 +}
 +
 +/* A single TPACKET_V3 block can hold multiple frames */
 +static bool recv_block(struct ring_state *ring)
 +{
 +      struct tpacket_block_desc *block;
 +      char *frame;
 +      int i;
 +
 +      block = (void *)(ring->mmap + ring->idx * ring_block_sz);
 +      if (!(block->hdr.bh1.block_status & TP_STATUS_USER))
 +              return false;
 +
 +      frame = (char *)block;
 +      frame += block->hdr.bh1.offset_to_first_pkt;
 +
 +      for (i = 0; i < block->hdr.bh1.num_pkts; i++) {
 +              frame = recv_frame(ring, frame);
 +              frames_received++;
 +      }
 +
 +      block->hdr.bh1.block_status = TP_STATUS_KERNEL;
 +      ring->idx = (ring->idx + 1) % ring_block_nr;
 +
 +      return true;
 +}
 +
 +/* simple test: sleep once unconditionally and then process all rings */
 +static void process_rings(void)
 +{
 +      int i;
 +
 +      usleep(1000 * cfg_timeout_msec);
 +
 +      for (i = 0; i < num_cpus; i++)
 +              do {} while (recv_block(&rings[i]));
 +
 +      fprintf(stderr, "count: pass=%u nohash=%u fail=%u\n",
 +              frames_received - frames_nohash - frames_error,
 +              frames_nohash, frames_error);
 +}
 +
 +static char *setup_ring(int fd)
 +{
 +      struct tpacket_req3 req3 = {0};
 +      void *ring;
 +
 +      req3.tp_retire_blk_tov = cfg_timeout_msec / 8;
 +      req3.tp_feature_req_word = TP_FT_REQ_FILL_RXHASH;
 +
 +      req3.tp_frame_size = 2048;
 +      req3.tp_frame_nr = 1 << 10;
 +      req3.tp_block_nr = 16;
 +
 +      req3.tp_block_size = req3.tp_frame_size * req3.tp_frame_nr;
 +      req3.tp_block_size /= req3.tp_block_nr;
 +
 +      if (setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &req3, sizeof(req3)))
 +              error(1, errno, "setsockopt PACKET_RX_RING");
 +
 +      ring_block_sz = req3.tp_block_size;
 +      ring_block_nr = req3.tp_block_nr;
 +
 +      ring = mmap(0, req3.tp_block_size * req3.tp_block_nr,
 +                  PROT_READ | PROT_WRITE,
 +                  MAP_SHARED | MAP_LOCKED | MAP_POPULATE, fd, 0);
 +      if (ring == MAP_FAILED)
 +              error(1, 0, "mmap failed");
 +
 +      return ring;
 +}
 +
 +static void __set_filter(int fd, int off_proto, uint8_t proto, int off_dport)
 +{
 +      struct sock_filter filter[] = {
 +              BPF_STMT(BPF_LD  + BPF_B   + BPF_ABS, SKF_AD_OFF + SKF_AD_PKTTYPE),
 +              BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, PACKET_HOST, 0, 4),
 +              BPF_STMT(BPF_LD  + BPF_B   + BPF_ABS, off_proto),
 +              BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, proto, 0, 2),
 +              BPF_STMT(BPF_LD  + BPF_H   + BPF_ABS, off_dport),
 +              BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, cfg_dport, 1, 0),
 +              BPF_STMT(BPF_RET + BPF_K, 0),
 +              BPF_STMT(BPF_RET + BPF_K, 0xFFFF),
 +      };
 +      struct sock_fprog prog = {};
 +
 +      prog.filter = filter;
 +      prog.len = ARRAY_SIZE(filter);
 +      if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)))
 +              error(1, errno, "setsockopt filter");
 +}
 +
 +/* filter on transport protocol and destination port */
 +static void set_filter(int fd)
 +{
 +      const int off_dport = offsetof(struct tcphdr, dest);    /* same for udp */
 +      uint8_t proto;
 +
 +      proto = cfg_type == SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP;
 +      if (cfg_family == AF_INET)
 +              __set_filter(fd, offsetof(struct iphdr, protocol), proto,
 +                           sizeof(struct iphdr) + off_dport);
 +      else
 +              __set_filter(fd, offsetof(struct ip6_hdr, ip6_nxt), proto,
 +                           sizeof(struct ip6_hdr) + off_dport);
 +}
 +
 +/* drop everything: used temporarily during setup */
 +static void set_filter_null(int fd)
 +{
 +      struct sock_filter filter[] = {
 +              BPF_STMT(BPF_RET + BPF_K, 0),
 +      };
 +      struct sock_fprog prog = {};
 +
 +      prog.filter = filter;
 +      prog.len = ARRAY_SIZE(filter);
 +      if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)))
 +              error(1, errno, "setsockopt filter");
 +}
 +
 +static int create_ring(char **ring)
 +{
 +      struct fanout_args args = {
 +              .id = 1,
 +              .type_flags = PACKET_FANOUT_CPU,
 +              .max_num_members = RSS_MAX_CPUS
 +      };
 +      struct sockaddr_ll ll = { 0 };
 +      int fd, val;
 +
 +      fd = socket(PF_PACKET, SOCK_DGRAM, 0);
 +      if (fd == -1)
 +              error(1, errno, "socket creation failed");
 +
 +      val = TPACKET_V3;
 +      if (setsockopt(fd, SOL_PACKET, PACKET_VERSION, &val, sizeof(val)))
 +              error(1, errno, "setsockopt PACKET_VERSION");
 +      *ring = setup_ring(fd);
 +
 +      /* block packets until all rings are added to the fanout group:
 +       * else packets can arrive during setup and get misclassified
 +       */
 +      set_filter_null(fd);
 +
 +      ll.sll_family = AF_PACKET;
 +      ll.sll_ifindex = if_nametoindex(cfg_ifname);
 +      ll.sll_protocol = cfg_family == AF_INET ? htons(ETH_P_IP) :
 +                                                htons(ETH_P_IPV6);
 +      if (bind(fd, (void *)&ll, sizeof(ll)))
 +              error(1, errno, "bind");
 +
 +      /* must come after bind: verifies all programs in group match */
 +      if (setsockopt(fd, SOL_PACKET, PACKET_FANOUT, &args, sizeof(args))) {
 +              /* on failure, retry using old API if that is sufficient:
 +               * it has a hard limit of 256 sockets, so only try if
 +               * (a) only testing rxhash, not RSS or (b) <= 256 cpus.
 +               * in this API, the third argument is left implicit.
 +               */
 +              if (cfg_num_queues || num_cpus > 256 ||
 +                  setsockopt(fd, SOL_PACKET, PACKET_FANOUT,
 +                             &args, sizeof(uint32_t)))
 +                      error(1, errno, "setsockopt PACKET_FANOUT cpu");
 +      }
 +
 +      return fd;
 +}
 +
 +/* setup inet(6) socket to blackhole the test traffic, if arg '-s' */
 +static int setup_sink(void)
 +{
 +      int fd, val;
 +
 +      fd = socket(cfg_family, cfg_type, 0);
 +      if (fd == -1)
 +              error(1, errno, "socket %d.%d", cfg_family, cfg_type);
 +
 +      val = 1 << 20;
 +      if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &val, sizeof(val)))
 +              error(1, errno, "setsockopt rcvbuf");
 +
 +      return fd;
 +}
 +
 +static void setup_rings(void)
 +{
 +      int i;
 +
 +      for (i = 0; i < num_cpus; i++) {
 +              rings[i].cpu = i;
 +              rings[i].fd = create_ring(&rings[i].mmap);
 +      }
 +
 +      /* accept packets once all rings in the fanout group are up */
 +      for (i = 0; i < num_cpus; i++)
 +              set_filter(rings[i].fd);
 +}
 +
 +static void cleanup_rings(void)
 +{
 +      int i;
 +
 +      for (i = 0; i < num_cpus; i++) {
 +              if (munmap(rings[i].mmap, ring_block_nr * ring_block_sz))
 +                      error(1, errno, "munmap");
 +              if (close(rings[i].fd))
 +                      error(1, errno, "close");
 +      }
 +}
 +
 +static void parse_cpulist(const char *arg)
 +{
 +      do {
 +              rx_irq_cpus[cfg_num_queues++] = strtol(arg, NULL, 10);
 +
 +              arg = strchr(arg, ',');
 +              if (!arg)
 +                      break;
 +              arg++;                  // skip ','
 +      } while (1);
 +}
 +
 +static void show_cpulist(void)
 +{
 +      int i;
 +
 +      for (i = 0; i < cfg_num_queues; i++)
 +              fprintf(stderr, "rxq %d: cpu %d\n", i, rx_irq_cpus[i]);
 +}
 +
 +static void show_silos(void)
 +{
 +      int i;
 +
 +      for (i = 0; i < cfg_num_rps_cpus; i++)
 +              fprintf(stderr, "silo %d: cpu %d\n", i, rps_silo_to_cpu[i]);
 +}
 +
 +static void parse_toeplitz_key(const char *str, int slen, unsigned char *key)
 +{
 +      int i, ret, off;
 +
 +      if (slen < TOEPLITZ_STR_MIN_LEN ||
 +          slen > TOEPLITZ_STR_MAX_LEN + 1)
 +              error(1, 0, "invalid toeplitz key");
 +
 +      for (i = 0, off = 0; off < slen; i++, off += 3) {
 +              ret = sscanf(str + off, "%hhx", &key[i]);
 +              if (ret != 1)
 +                      error(1, 0, "key parse error at %d off %d len %d",
 +                            i, off, slen);
 +      }
 +}
 +
 +static void parse_rps_bitmap(const char *arg)
 +{
 +      unsigned long bitmap;
 +      int i;
 +
 +      bitmap = strtoul(arg, NULL, 0);
 +
 +      if (bitmap & ~(RPS_MAX_CPUS - 1))
 +              error(1, 0, "rps bitmap 0x%lx out of bounds 0..%lu",
 +                    bitmap, RPS_MAX_CPUS - 1);
 +
 +      for (i = 0; i < RPS_MAX_CPUS; i++)
 +              if (bitmap & 1UL << i)
 +                      rps_silo_to_cpu[cfg_num_rps_cpus++] = i;
 +}
 +
 +static void read_rss_dev_info_ynl(void)
 +{
 +      struct ethtool_rss_get_req *req;
 +      struct ethtool_rss_get_rsp *rsp;
 +      struct ynl_sock *ys;
 +
 +      ys = ynl_sock_create(&ynl_ethtool_family, NULL);
 +      if (!ys)
 +              error(1, errno, "ynl_sock_create failed");
 +
 +      req = ethtool_rss_get_req_alloc();
 +      if (!req)
 +              error(1, errno, "ethtool_rss_get_req_alloc failed");
 +
 +      ethtool_rss_get_req_set_header_dev_name(req, cfg_ifname);
 +
 +      rsp = ethtool_rss_get(ys, req);
 +      if (!rsp)
 +              error(1, ys->err.code, "YNL: %s", ys->err.msg);
 +
 +      if (!rsp->_len.hkey)
 +              error(1, 0, "RSS key not available for %s", cfg_ifname);
 +
 +      if (rsp->_len.hkey < TOEPLITZ_KEY_MIN_LEN ||
 +          rsp->_len.hkey > TOEPLITZ_KEY_MAX_LEN)
 +              error(1, 0, "RSS key length %u out of bounds [%u, %u]",
 +                    rsp->_len.hkey, TOEPLITZ_KEY_MIN_LEN,
 +                    TOEPLITZ_KEY_MAX_LEN);
 +
 +      memcpy(toeplitz_key, rsp->hkey, rsp->_len.hkey);
 +
 +      if (rsp->_count.indir > RSS_MAX_INDIR)
 +              error(1, 0, "RSS indirection table too large (%u > %u)",
 +                    rsp->_count.indir, RSS_MAX_INDIR);
 +
 +      /* If indir table not available we'll fallback to simple modulo math */
 +      if (rsp->_count.indir) {
 +              memcpy(rss_indir_tbl, rsp->indir,
 +                     rsp->_count.indir * sizeof(rss_indir_tbl[0]));
 +              rss_indir_tbl_size = rsp->_count.indir;
 +
 +              log_verbose("RSS indirection table size: %u\n",
 +                          rss_indir_tbl_size);
 +      }
 +
 +      ethtool_rss_get_rsp_free(rsp);
 +      ethtool_rss_get_req_free(req);
 +      ynl_sock_destroy(ys);
 +}
 +
 +static void parse_opts(int argc, char **argv)
 +{
 +      static struct option long_options[] = {
 +          {"dport",   required_argument, 0, 'd'},
 +          {"cpus",    required_argument, 0, 'C'},
 +          {"key",     required_argument, 0, 'k'},
 +          {"iface",   required_argument, 0, 'i'},
 +          {"ipv4",    no_argument, 0, '4'},
 +          {"ipv6",    no_argument, 0, '6'},
 +          {"sink",    no_argument, 0, 's'},
 +          {"tcp",     no_argument, 0, 't'},
 +          {"timeout", required_argument, 0, 'T'},
 +          {"udp",     no_argument, 0, 'u'},
 +          {"verbose", no_argument, 0, 'v'},
 +          {"rps",     required_argument, 0, 'r'},
 +          {0, 0, 0, 0}
 +      };
 +      bool have_toeplitz = false;
 +      int index, c;
 +
 +      while ((c = getopt_long(argc, argv, "46C:d:i:k:r:stT:uv", long_options, &index)) != -1) {
 +              switch (c) {
 +              case '4':
 +                      cfg_family = AF_INET;
 +                      break;
 +              case '6':
 +                      cfg_family = AF_INET6;
 +                      break;
 +              case 'C':
 +                      parse_cpulist(optarg);
 +                      break;
 +              case 'd':
 +                      cfg_dport = strtol(optarg, NULL, 0);
 +                      break;
 +              case 'i':
 +                      cfg_ifname = optarg;
 +                      break;
 +              case 'k':
 +                      parse_toeplitz_key(optarg, strlen(optarg),
 +                                         toeplitz_key);
 +                      have_toeplitz = true;
 +                      break;
 +              case 'r':
 +                      parse_rps_bitmap(optarg);
 +                      break;
 +              case 's':
 +                      cfg_sink = true;
 +                      break;
 +              case 't':
 +                      cfg_type = SOCK_STREAM;
 +                      break;
 +              case 'T':
 +                      cfg_timeout_msec = strtol(optarg, NULL, 0);
 +                      break;
 +              case 'u':
 +                      cfg_type = SOCK_DGRAM;
 +                      break;
 +              case 'v':
 +                      cfg_verbose = true;
 +                      break;
 +
 +              default:
 +                      error(1, 0, "unknown option %c", optopt);
 +                      break;
 +              }
 +      }
 +
 +      if (!have_toeplitz)
 +              read_rss_dev_info_ynl();
 +
 +      num_cpus = get_nprocs();
 +      if (num_cpus > RSS_MAX_CPUS)
 +              error(1, 0, "increase RSS_MAX_CPUS");
 +
 +      if (cfg_num_queues && cfg_num_rps_cpus)
 +              error(1, 0,
 +                    "Can't supply both RSS cpus ('-C') and RPS map ('-r')");
 +      if (cfg_verbose) {
 +              show_cpulist();
 +              show_silos();
 +      }
 +}
 +
 +int main(int argc, char **argv)
 +{
 +      const int min_tests = 10;
 +      int fd_sink = -1;
 +
 +      parse_opts(argc, argv);
 +
 +      if (cfg_sink)
 +              fd_sink = setup_sink();
 +
 +      setup_rings();
 +
 +      /* Signal to test framework that we're ready to receive */
 +      ksft_ready();
 +
 +      process_rings();
 +      cleanup_rings();
 +
 +      if (cfg_sink && close(fd_sink))
 +              error(1, errno, "close sink");
 +
 +      if (frames_received - frames_nohash < min_tests)
 +              error(1, 0, "too few frames for verification");
 +
 +      return frames_error;
 +}
Simple merge
Simple merge
Simple merge
  #include <linux/fs.h>
  #include <linux/limits.h>
  #include <linux/nsfs.h>
- #include "../kselftest_harness.h"
+ #include "kselftest_harness.h"
  
 +/* Fixture for tests that create child processes */
 +FIXTURE(nsid) {
 +      pid_t child_pid;
 +};
 +
 +FIXTURE_SETUP(nsid) {
 +      self->child_pid = 0;
 +}
 +
 +FIXTURE_TEARDOWN(nsid) {
 +      /* Clean up any child process that may still be running */
 +      if (self->child_pid > 0) {
 +              kill(self->child_pid, SIGKILL);
 +              waitpid(self->child_pid, NULL, 0);
 +      }
 +}
 +
  TEST(nsid_mntns_basic)
  {
        __u64 mnt_ns_id = 0;
Simple merge
Simple merge
  #include <linux/compiler.h>
  #include <linux/kernel.h>
  #include <asm/ucontext.h>
 +#include <getopt.h>
  
  #include "hwprobe.h"
- #include "../../kselftest.h"
+ #include "kselftest.h"
  
  #define MK_CBO(fn) le32_bswap((uint32_t)(fn) << 20 | 10 << 15 | 2 << 12 | 0 << 7 | 15)
 +#define MK_PREFETCH(fn) \
 +      le32_bswap(0 << 25 | (uint32_t)(fn) << 20 | 10 << 15 | 6 << 12 | 0 << 7 | 19)
  
  static char mem[4096] __aligned(4096) = { [0 ... 4095] = 0xa5 };
  
@@@ -18,9 -18,8 +18,9 @@@
  #include <time.h>
  #include <include/vdso/time64.h>
  #include <pthread.h>
 +#include <stdbool.h>
  
- #include "../kselftest.h"
+ #include "kselftest.h"
  
  #define DELAY 2
  
  #include <sys/mman.h>
  
  #include <uapi/linux/types.h>
 +#include <linux/iommufd.h>
  #include <linux/limits.h>
  #include <linux/mman.h>
 +#include <linux/overflow.h>
  #include <linux/types.h>
  #include <linux/vfio.h>
 -#include <linux/iommufd.h>
  
- #include "../../../kselftest.h"
+ #include "kselftest.h"
 -#include <vfio_util.h>
 +#include <libvfio.h>
  
  #define PCI_SYSFS_PATH        "/sys/bus/pci/devices"
  
@@@ -1,6 -1,8 +1,6 @@@
  // SPDX-License-Identifier: GPL-2.0-only
- #include "../../../kselftest.h"
 -#include <stdio.h>
 -
+ #include "kselftest.h"
 -#include <vfio_util.h>
 +#include <libvfio.h>
  
  #ifdef __x86_64__
  extern struct vfio_pci_driver_ops dsa_ops;
@@@ -10,9 -8,9 +10,9 @@@
  #include <linux/sizes.h>
  #include <linux/vfio.h>
  
 -#include <vfio_util.h>
 +#include <libvfio.h>
  
- #include "../kselftest_harness.h"
+ #include "kselftest_harness.h"
  
  static const char *device_bdf;
  
@@@ -10,8 -10,8 +10,8 @@@
  #include <sys/ioctl.h>
  #include <unistd.h>
  
 -#include <vfio_util.h>
 +#include <libvfio.h>
- #include "../kselftest_harness.h"
+ #include "kselftest_harness.h"
  
  static const char iommu_dev_path[] = "/dev/iommu";
  static const char *cdev_path;
@@@ -10,9 -10,9 +10,9 @@@
  #include <linux/sizes.h>
  #include <linux/vfio.h>
  
 -#include <vfio_util.h>
 +#include <libvfio.h>
  
- #include "../kselftest_harness.h"
+ #include "kselftest_harness.h"
  
  static const char *device_bdf;
  
@@@ -5,9 -5,9 +5,9 @@@
  #include <linux/sizes.h>
  #include <linux/vfio.h>
  
 -#include <vfio_util.h>
 +#include <libvfio.h>
  
- #include "../kselftest_harness.h"
+ #include "kselftest_harness.h"
  
  static const char *device_bdf;