qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] thunk, linux-user: Add support for SIOCETHTOOL ioctl
@ 2020-12-18 21:41 Shu-Chun Weng via
  2020-12-18 21:41 ` [PATCH 1/2] thunk: supports flexible arrays Shu-Chun Weng via
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Shu-Chun Weng via @ 2020-12-18 21:41 UTC (permalink / raw)
  To: qemu-devel; +Cc: Riku Voipio, Shu-Chun Weng, Laurent Vivier

Hi Riku and Laurent,

These two patches together implements the SIOCETHTOOL ioctl operations. Many of
the kernel structures in this operations make use of flexible arrays (array with
no size / zero size at the end of the structure), so I extended thunk to handle
these fields natively.

This patch set was previously sent out under "[PATCH 0/6] fcntl, sockopt, and
ioctl options" and the following revisions, but did not get any feedback.

Shu-Chun Weng (2):
  thunk: supports flexible arrays
  linux-user: Add support for SIOCETHTOOL ioctl

 include/exec/user/thunk.h     |  24 +
 linux-user/ethtool.c          | 848 ++++++++++++++++++++++++++++++++++
 linux-user/ethtool.h          |  20 +
 linux-user/ethtool_entries.h  | 107 +++++
 linux-user/ioctls.h           |   2 +
 linux-user/meson.build        |   1 +
 linux-user/qemu.h             |   1 +
 linux-user/syscall.c          |  36 +-
 linux-user/syscall_defs.h     |  12 +
 linux-user/syscall_types.h    | 280 +++++++++++
 tests/tcg/multiarch/ethtool.c | 423 +++++++++++++++++
 thunk.c                       | 152 +++++-
 12 files changed, 1893 insertions(+), 13 deletions(-)
 create mode 100644 linux-user/ethtool.c
 create mode 100644 linux-user/ethtool.h
 create mode 100644 linux-user/ethtool_entries.h
 create mode 100644 tests/tcg/multiarch/ethtool.c

-- 
2.29.2.684.gfbc64c5ab5-goog



^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 1/2] thunk: supports flexible arrays
  2020-12-18 21:41 [PATCH 0/2] thunk, linux-user: Add support for SIOCETHTOOL ioctl Shu-Chun Weng via
@ 2020-12-18 21:41 ` Shu-Chun Weng via
  2020-12-18 21:41 ` [PATCH 2/2] linux-user: Add support for SIOCETHTOOL ioctl Shu-Chun Weng via
  2020-12-18 21:51 ` [PATCH 0/2] thunk, " no-reply
  2 siblings, 0 replies; 4+ messages in thread
From: Shu-Chun Weng via @ 2020-12-18 21:41 UTC (permalink / raw)
  To: qemu-devel; +Cc: Riku Voipio, Shu-Chun Weng

Flexible arrays may appear in the last field of a struct and are heavily
used in the ioctl(SIOCETHTOOL) system call on Linux. E.g.

  struct ethtool_regs {
      __u32   cmd;
      __u32   version; /* driver-specific, indicates different chips/revs */
      __u32   len; /* bytes */
      __u8    data[0];
  };

where number of elements in `data` is specified in `len`. It is translated
into:

  STRUCT(ethtool_regs,
         TYPE_INT, /* cmd */
         TYPE_INT, /* version */
         TYPE_INT, /* len */
         MK_FLEXIBLE_ARRAY(TYPE_CHAR, 2)) /* data[0]: len */

where the "2" passed to `MK_FLEXIBLE_ARRAY` means the number of element
is specified by field number 2 (0-index).

Signed-off-by: Shu-Chun Weng <scw@google.com>
---
 include/exec/user/thunk.h |  24 ++++++
 thunk.c                   | 152 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 174 insertions(+), 2 deletions(-)

diff --git a/include/exec/user/thunk.h b/include/exec/user/thunk.h
index 300a840d58..42c58db7f0 100644
--- a/include/exec/user/thunk.h
+++ b/include/exec/user/thunk.h
@@ -39,12 +39,21 @@ typedef enum argtype {
     TYPE_ARRAY,
     TYPE_STRUCT,
     TYPE_OLDDEVT,
+    TYPE_FLEXIBLE_ARRAY,
 } argtype;
 
 #define MK_PTR(type) TYPE_PTR, type
 #define MK_ARRAY(type, size) TYPE_ARRAY, (int)(size), type
 #define MK_STRUCT(id) TYPE_STRUCT, id
 
+/*
+ * Should only appear as the last element of a TYPE_STRUCT. `len_field_idx` is
+ * the index into the fields in the enclosing struct that specify the length of
+ * the flexibly array. The length field MUST be a TYPE_INT field.
+ */
+#define MK_FLEXIBLE_ARRAY(type, len_field_idx) \
+    TYPE_FLEXIBLE_ARRAY, (len_field_idx), type
+
 #define THUNK_TARGET 0
 #define THUNK_HOST   1
 
@@ -56,6 +65,8 @@ typedef struct {
     /* special handling */
     void (*convert[2])(void *dst, const void *src);
     void (*print)(void *arg);
+    int (*thunk_size[2])(const void *src);
+
     int size[2];
     int align[2];
     const char *name;
@@ -76,6 +87,11 @@ const argtype *thunk_convert(void *dst, const void *src,
                              const argtype *type_ptr, int to_host);
 const argtype *thunk_print(void *arg, const argtype *type_ptr);
 
+bool thunk_type_has_flexible_array(const argtype *type_ptr);
+/* thunk_type_size but can handle TYPE_FLEXIBLE_ARRAY */
+int thunk_type_size_with_src(const void *src, const argtype *type_ptr,
+                             int is_host);
+
 extern StructEntry *struct_entries;
 
 int thunk_type_size_array(const argtype *type_ptr, int is_host);
@@ -138,6 +154,12 @@ static inline int thunk_type_size(const argtype *type_ptr, int is_host)
     case TYPE_STRUCT:
         se = struct_entries + type_ptr[1];
         return se->size[is_host];
+    case TYPE_FLEXIBLE_ARRAY:
+        /*
+         * Flexible arrays do not count toward sizeof(). Users of structures
+         * containing them need to calculate it themselves.
+         */
+        return 0;
     default:
         g_assert_not_reached();
     }
@@ -188,6 +210,8 @@ static inline int thunk_type_align(const argtype *type_ptr, int is_host)
     case TYPE_STRUCT:
         se = struct_entries + type_ptr[1];
         return se->align[is_host];
+    case TYPE_FLEXIBLE_ARRAY:
+        return thunk_type_align_array(type_ptr + 2, is_host);
     default:
         g_assert_not_reached();
     }
diff --git a/thunk.c b/thunk.c
index fc5be1a502..f13e96cc4f 100644
--- a/thunk.c
+++ b/thunk.c
@@ -50,6 +50,8 @@ static inline const argtype *thunk_type_next(const argtype *type_ptr)
         return thunk_type_next_ptr(type_ptr + 1);
     case TYPE_STRUCT:
         return type_ptr + 1;
+    case TYPE_FLEXIBLE_ARRAY:
+        return thunk_type_next_ptr(type_ptr + 1);
     default:
         return NULL;
     }
@@ -122,6 +124,34 @@ void thunk_register_struct_direct(int id, const char *name,
     se->name = name;
 }
 
+static const argtype *
+thunk_convert_flexible_array(void *dst, const void *src,
+                             const uint8_t *dst_struct,
+                             const uint8_t *src_struct, const argtype *type_ptr,
+                             const StructEntry *se, int to_host) {
+    int len_field_idx, dst_size, src_size, i;
+    uint32_t array_length;
+    uint8_t *d;
+    const uint8_t *s;
+
+    assert(*type_ptr == TYPE_FLEXIBLE_ARRAY);
+    type_ptr++;
+    len_field_idx = *type_ptr++;
+    array_length =
+        *(const uint32_t *)(to_host ?
+                            dst_struct + se->field_offsets[1][len_field_idx] :
+                            src_struct + se->field_offsets[0][len_field_idx]);
+    dst_size = thunk_type_size(type_ptr, to_host);
+    src_size = thunk_type_size(type_ptr, to_host);
+    d = dst;
+    s = src;
+    for (i = 0; i < array_length; i++) {
+        thunk_convert(d, s, type_ptr, to_host);
+        d += dst_size;
+        s += src_size;
+    }
+    return thunk_type_next(type_ptr);
+}
 
 /* now we can define the main conversion functions */
 const argtype *thunk_convert(void *dst, const void *src,
@@ -246,7 +276,7 @@ const argtype *thunk_convert(void *dst, const void *src,
 
             assert(*type_ptr < max_struct_entries);
             se = struct_entries + *type_ptr++;
-            if (se->convert[0] != NULL) {
+            if (se->convert[to_host] != NULL) {
                 /* specific conversion is needed */
                 (*se->convert[to_host])(dst, src);
             } else {
@@ -256,7 +286,18 @@ const argtype *thunk_convert(void *dst, const void *src,
                 src_offsets = se->field_offsets[1 - to_host];
                 d = dst;
                 s = src;
-                for(i = 0;i < se->nb_fields; i++) {
+                for (i = 0; i < se->nb_fields; i++) {
+                    if (*field_types == TYPE_FLEXIBLE_ARRAY) {
+                        field_types = thunk_convert_flexible_array(
+                            d + dst_offsets[i],
+                            s + src_offsets[i],
+                            d,
+                            s,
+                            field_types,
+                            se,
+                            to_host);
+                        continue;
+                    }
                     field_types = thunk_convert(d + dst_offsets[i],
                                                 s + src_offsets[i],
                                                 field_types, to_host);
@@ -264,6 +305,11 @@ const argtype *thunk_convert(void *dst, const void *src,
             }
         }
         break;
+    case TYPE_FLEXIBLE_ARRAY:
+        fprintf(stderr,
+                "Invalid flexible array (type 0x%x) outside of a structure\n",
+                type);
+        break;
     default:
         fprintf(stderr, "Invalid type 0x%x\n", type);
         break;
@@ -271,6 +317,45 @@ const argtype *thunk_convert(void *dst, const void *src,
     return type_ptr;
 }
 
+static const argtype *
+thunk_print_flexible_array(void *arg, const uint8_t *arg_struct,
+                           const argtype *type_ptr, const StructEntry *se) {
+    int array_length, len_field_idx, arg_size, i;
+    uint8_t *a;
+    int is_string = 0;
+
+    assert(*type_ptr == TYPE_FLEXIBLE_ARRAY);
+    type_ptr++;
+    len_field_idx = *type_ptr++;
+
+    array_length = tswap32(
+        *(const uint32_t *)(arg_struct + se->field_offsets[0][len_field_idx]));
+    arg_size = thunk_type_size(type_ptr, 0);
+    a = arg;
+
+    if (*type_ptr == TYPE_CHAR) {
+        qemu_log("\"");
+        is_string = 1;
+    } else {
+        qemu_log("[");
+    }
+
+    for (i = 0; i < array_length; i++) {
+        if (i > 0 && !is_string) {
+            qemu_log(",");
+        }
+        thunk_print(a, type_ptr);
+        a += arg_size;
+    }
+
+    if (is_string) {
+        qemu_log("\"");
+    } else {
+        qemu_log("]");
+    }
+    return thunk_type_next(type_ptr);
+}
+
 const argtype *thunk_print(void *arg, const argtype *type_ptr)
 {
     int type;
@@ -418,18 +503,81 @@ const argtype *thunk_print(void *arg, const argtype *type_ptr)
                     if (i > 0) {
                         qemu_log(",");
                     }
+                    if (*field_types == TYPE_FLEXIBLE_ARRAY) {
+                        field_types = thunk_print_flexible_array(
+                            a + arg_offsets[i], a, field_types, se);
+                      continue;
+                    }
                     field_types = thunk_print(a + arg_offsets[i], field_types);
                 }
                 qemu_log("}");
             }
         }
         break;
+    case TYPE_FLEXIBLE_ARRAY:
+        fprintf(stderr,
+                "Invalid flexible array (type 0x%x) outside of a structure\n",
+                type);
+        break;
     default:
         g_assert_not_reached();
     }
     return type_ptr;
 }
 
+bool thunk_type_has_flexible_array(const argtype *type_ptr)
+{
+  int i;
+  const StructEntry *se;
+  const argtype *field_types;
+    if (*type_ptr != TYPE_STRUCT) {
+        return false;
+    }
+    se = struct_entries + type_ptr[1];
+    field_types = se->field_types;
+    for (i = 0; i < se->nb_fields; i++) {
+        if (*field_types == TYPE_FLEXIBLE_ARRAY) {
+            return true;
+        }
+        field_types = thunk_type_next(type_ptr);
+    }
+    return false;
+}
+
+int thunk_type_size_with_src(const void *src, const argtype *type_ptr,
+                             int is_host)
+{
+    switch (*type_ptr) {
+    case TYPE_STRUCT: {
+        int i;
+        const StructEntry *se = struct_entries + type_ptr[1];
+        const argtype *field_types;
+        if (se->thunk_size[is_host] != NULL) {
+            return (*se->thunk_size[is_host])(src);
+        }
+
+        field_types = se->field_types;
+        for (i = 0; i < se->nb_fields; i++) {
+            if (*field_types == TYPE_FLEXIBLE_ARRAY) {
+                uint32_t array_length = *(const uint32_t *)(
+                    (const uint8_t *)src +
+                    se->field_offsets[is_host][field_types[1]]);
+                if (!is_host) {
+                    array_length = tswap32(array_length);
+                }
+                return se->size[is_host] +
+                    array_length *
+                    thunk_type_size(field_types + 2, is_host);
+            }
+            field_types = thunk_type_next(type_ptr);
+        }
+        return se->size[is_host];
+    }
+    default:
+        return thunk_type_size(type_ptr, is_host);
+    }
+}
+
 /* from em86 */
 
 /* Utility function: Table-driven functions to translate bitmasks
-- 
2.29.2.684.gfbc64c5ab5-goog



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/2] linux-user: Add support for SIOCETHTOOL ioctl
  2020-12-18 21:41 [PATCH 0/2] thunk, linux-user: Add support for SIOCETHTOOL ioctl Shu-Chun Weng via
  2020-12-18 21:41 ` [PATCH 1/2] thunk: supports flexible arrays Shu-Chun Weng via
@ 2020-12-18 21:41 ` Shu-Chun Weng via
  2020-12-18 21:51 ` [PATCH 0/2] thunk, " no-reply
  2 siblings, 0 replies; 4+ messages in thread
From: Shu-Chun Weng via @ 2020-12-18 21:41 UTC (permalink / raw)
  To: qemu-devel; +Cc: Shu-Chun Weng, Laurent Vivier

The ioctl numeric values are platform-independent and determined by
the file include/uapi/linux/sockios.h in Linux kernel source code:

  #define SIOCETHTOOL   0x8946

These ioctls get (or set) various structures pointed by the field
ifr_data in the structure ifreq depending on the first 4 bytes of the
memory region.

This change clones the ioctl framework into ethtool-specific dispatch
logic in its own file. A number of definitions previously only visible
in syscall.c are thus exported to syscall_defs.h to be used in the new
files.

Signed-off-by: Shu-Chun Weng <scw@google.com>
---
 linux-user/ethtool.c          | 848 ++++++++++++++++++++++++++++++++++
 linux-user/ethtool.h          |  20 +
 linux-user/ethtool_entries.h  | 107 +++++
 linux-user/ioctls.h           |   2 +
 linux-user/meson.build        |   1 +
 linux-user/qemu.h             |   1 +
 linux-user/syscall.c          |  36 +-
 linux-user/syscall_defs.h     |  12 +
 linux-user/syscall_types.h    | 280 +++++++++++
 tests/tcg/multiarch/ethtool.c | 423 +++++++++++++++++
 10 files changed, 1719 insertions(+), 11 deletions(-)
 create mode 100644 linux-user/ethtool.c
 create mode 100644 linux-user/ethtool.h
 create mode 100644 linux-user/ethtool_entries.h
 create mode 100644 tests/tcg/multiarch/ethtool.c

diff --git a/linux-user/ethtool.c b/linux-user/ethtool.c
new file mode 100644
index 0000000000..6f6b84153a
--- /dev/null
+++ b/linux-user/ethtool.c
@@ -0,0 +1,848 @@
+/*
+ *  Linux ioctl system call SIOCETHTOOL requests
+ *
+ *  Copyright (c) 2020 Shu-Chun Weng
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "qemu/osdep.h"
+#include <stdio.h>
+#include <linux/ethtool.h>
+#include <linux/if.h>
+#include <linux/sockios.h>
+#include <linux/unistd.h>
+#ifdef HAVE_BTRFS_H
+/*
+ * Presence of the BTRFS macros affects the STRUCT_* macro values because the
+ * order in syscall_types.h matters but some entries are dropped when the BTRFS
+ * macros are not defined. We must include linux/btrfs.h before any file first
+ * transitively include syscall_types.h.
+ */
+#include <linux/btrfs.h>
+#endif
+#include "ethtool.h"
+#include "qemu.h"
+
+/* Non-standard ethtool structure definitions. */
+/*
+ * struct ethtool_rxnfc {
+ *     __u32 cmd;
+ *     __u32 flow_type;
+ *     __u64 data;
+ *     struct ethtool_rx_flow_spec fs;
+ *     union {
+ *         __u32 rule_cnt;
+ *         __u32 rss_context;
+ *     };
+ *     __u32 rule_locs[0];
+ * };
+ *
+ * Originally defined for ETHTOOL_{G,S}RXFH with only the cmd, flow_type and
+ * data members. For other commands, dedicated standard structure definitions
+ * are listed in syscall_types.h.
+ */
+static void host_to_target_ethtool_rxnfc_get_set_rxfh(void *dst,
+                                                      const void *src)
+{
+    static const argtype ethtool_rx_flow_spec_argtype[] = {
+        MK_STRUCT(STRUCT_ethtool_rx_flow_spec), TYPE_NULL };
+    struct ethtool_rxnfc *target = dst;
+    const struct ethtool_rxnfc *host = src;
+
+    target->cmd = tswap32(host->cmd);
+    target->flow_type = tswap32(host->flow_type);
+    target->data = tswap64(host->data);
+
+    if (host->cmd == ETHTOOL_SRXFH) {
+        /*
+         * struct ethtool_rxnfc was originally defined for ETHTOOL_{G,S}RXFH
+         * with only the cmd, flow_type and data members. Guest program might
+         * still be using that definition.
+         */
+        return;
+    }
+    if (host->cmd != ETHTOOL_GRXFH) {
+        fprintf(stderr, "host_to_target_ethtool_rxnfc_get_set_rxfh called with "
+                "command 0x%x which is not ETHTOOL_SRXFH or ETHTOOL_GRXFH\n",
+                host->cmd);
+    }
+    if ((host->flow_type & FLOW_RSS) == 0) {
+        return;
+    }
+    /*
+     * If `FLOW_RSS` was requested then guest program must be using the new
+     * definition.
+     */
+    thunk_convert(&target->fs, &host->fs, ethtool_rx_flow_spec_argtype,
+                  THUNK_TARGET);
+    target->rule_cnt = tswap32(host->rule_cnt);
+}
+
+static void target_to_host_ethtool_rxnfc_get_set_rxfh(void *dst,
+                                                      const void *src)
+{
+    static const argtype ethtool_rx_flow_spec_argtype[] = {
+        MK_STRUCT(STRUCT_ethtool_rx_flow_spec), TYPE_NULL };
+    struct ethtool_rxnfc *host = dst;
+    const struct ethtool_rxnfc *target = src;
+
+    host->cmd = tswap32(target->cmd);
+    host->flow_type = tswap32(target->flow_type);
+    host->data = tswap64(target->data);
+
+    if (host->cmd == ETHTOOL_SRXFH) {
+        /*
+         * struct ethtool_rxnfc was originally defined for ETHTOOL_{G,S}RXFH
+         * with only the cmd, flow_type and data members. Guest program might
+         * still be using that definition.
+         */
+        return;
+    }
+    if (host->cmd != ETHTOOL_GRXFH) {
+        fprintf(stderr, "target_to_host_ethtool_rxnfc_get_set_rxfh called with "
+                "command 0x%x which is not ETHTOOL_SRXFH or ETHTOOL_GRXFH\n",
+                host->cmd);
+    }
+    if ((host->flow_type & FLOW_RSS) == 0) {
+        return;
+    }
+    /*
+     * If `FLOW_RSS` was requested then guest program must be using the new
+     * definition.
+     */
+    thunk_convert(&host->fs, &target->fs, ethtool_rx_flow_spec_argtype,
+                  THUNK_HOST);
+    host->rule_cnt = tswap32(target->rule_cnt);
+}
+
+static int target_ethtool_rxnfc_get_set_rxfh_size(const void *src)
+{
+    const struct ethtool_rxnfc *target = src;
+    int cmd = tswap32(target->cmd);
+    if (cmd == ETHTOOL_SRXFH ||
+        (cmd == ETHTOOL_GRXFH &&
+         (tswap32(target->flow_type) & FLOW_RSS) == 0)) {
+        return 16;
+    }
+    return sizeof(struct ethtool_rxnfc);
+}
+
+static int host_ethtool_rxnfc_get_set_rxfh_size(const void *src)
+{
+    const struct ethtool_rxnfc *host = src;
+    if (host->cmd == ETHTOOL_SRXFH ||
+        (host->cmd == ETHTOOL_GRXFH && (host->flow_type & FLOW_RSS) == 0)) {
+        return 16;
+    }
+    return sizeof(struct ethtool_rxnfc);
+}
+
+const StructEntry struct_ethtool_rxnfc_get_set_rxfh_def = {
+    .convert = {
+        host_to_target_ethtool_rxnfc_get_set_rxfh,
+        target_to_host_ethtool_rxnfc_get_set_rxfh },
+    .thunk_size = {
+        target_ethtool_rxnfc_get_set_rxfh_size,
+        host_ethtool_rxnfc_get_set_rxfh_size },
+    .size = { 16, 16 },
+    .align = {
+        __alignof__(struct ethtool_rxnfc),
+        __alignof__(struct ethtool_rxnfc) },
+};
+
+/*
+ * struct ethtool_sset_info {
+ *     __u32 cmd;
+ *     __u32 reserved;
+ *     __u64 sset_mask;
+ *     __u32 data[0];
+ * };
+ *
+ * `sset_mask` is a bitmask of string sets. `data` is the buffer for string set
+ * sizes, containing number of 1s in `sset_mask`'s binary representation number
+ * of 4-byte entries.
+ *
+ * Since all fields are fixed-width and number of 1s in `sset_mask` does not
+ * change between architectures, host-to-target and target-to-host are
+ * identical.
+ */
+static void convert_ethtool_sset_info(void *dst, const void *src)
+{
+    int i, set_count;
+    struct ethtool_sset_info *dst_sset_info = dst;
+    const struct ethtool_sset_info *src_sset_info = src;
+
+    dst_sset_info->cmd = tswap32(src_sset_info->cmd);
+    dst_sset_info->sset_mask = tswap64(src_sset_info->sset_mask);
+
+    set_count = ctpop64(src_sset_info->sset_mask);
+    for (i = 0; i < set_count; ++i) {
+        dst_sset_info->data[i] = tswap32(src_sset_info->data[i]);
+    }
+}
+
+static int ethtool_sset_info_size(const void *src)
+{
+    const struct ethtool_sset_info *src_sset_info = src;
+    return sizeof(struct ethtool_sset_info) +
+        ctpop64(src_sset_info->sset_mask) * sizeof(src_sset_info->data[0]);
+}
+
+const StructEntry struct_ethtool_sset_info_def = {
+    .convert = {
+        convert_ethtool_sset_info, convert_ethtool_sset_info },
+    .thunk_size = {
+        ethtool_sset_info_size, ethtool_sset_info_size },
+    .size = {
+        sizeof(struct ethtool_sset_info),
+        sizeof(struct ethtool_sset_info) },
+    .align = {
+        __alignof__(struct ethtool_sset_info),
+        __alignof__(struct ethtool_sset_info) },
+};
+
+/*
+ * struct ethtool_rxfh {
+ *     __u32 cmd;
+ *     __u32 rss_context;
+ *     __u32 indir_size;
+ *     __u32 key_size;
+ *     __u8  hfunc;
+ *     __u8  rsvd8[3];
+ *     __u32 rsvd32;
+ *     __u32 rss_config[0];
+ * };
+ *
+ * `rss_config`: indirection table of `indir_size` __u32 elements, followed by
+ * hash key of `key_size` bytes.
+ *
+ * `indir_size` could be ETH_RXFH_INDIR_NO_CHANGE when `cmd` is ETHTOOL_SRSSH
+ * and there would be no indircetion table in `rss_config`.
+ */
+static void convert_ethtool_rxfh_header(void *dst, const void *src)
+{
+    struct ethtool_rxfh *dst_rxfh = dst;
+    const struct ethtool_rxfh *src_rxfh = src;
+
+    dst_rxfh->cmd = tswap32(src_rxfh->cmd);
+    dst_rxfh->rss_context = tswap32(src_rxfh->rss_context);
+    dst_rxfh->indir_size = tswap32(src_rxfh->indir_size);
+    dst_rxfh->key_size = tswap32(src_rxfh->key_size);
+    dst_rxfh->hfunc = src_rxfh->hfunc;
+    dst_rxfh->rsvd8[0] = src_rxfh->rsvd8[0];
+    dst_rxfh->rsvd8[1] = src_rxfh->rsvd8[1];
+    dst_rxfh->rsvd8[2] = src_rxfh->rsvd8[2];
+    dst_rxfh->rsvd32 = tswap32(src_rxfh->rsvd32);
+}
+
+static void convert_ethtool_rxfh_rss_config(
+    void *dst, const void *src, uint32_t indir_size, uint32_t key_size) {
+    uint32_t *dst_rss_config = (uint32_t *)dst;
+    const uint32_t *src_rss_config = (const uint32_t *)src;
+    int i;
+    for (i = 0; i < indir_size; ++i) {
+        dst_rss_config[i] = tswap32(src_rss_config[i]);
+    }
+    if (key_size > 0) {
+        memcpy(dst_rss_config + indir_size,
+               src_rss_config + indir_size,
+               key_size);
+    }
+}
+
+static void host_to_target_ethtool_rxfh(void *dst, const void *src)
+{
+    struct ethtool_rxfh *target = dst;
+    const struct ethtool_rxfh *host = src;
+
+    convert_ethtool_rxfh_header(dst, src);
+
+    const uint32_t indir_size =
+        host->cmd == ETHTOOL_SRSSH &&
+        host->indir_size == ETH_RXFH_INDIR_NO_CHANGE ?
+        0 :
+        host->indir_size;
+    convert_ethtool_rxfh_rss_config(target->rss_config, host->rss_config,
+                                    indir_size, host->key_size);
+}
+
+static void target_to_host_ethtool_rxfh(void *dst, const void *src)
+{
+    struct ethtool_rxfh *host = dst;
+    const struct ethtool_rxfh *target = src;
+
+    convert_ethtool_rxfh_header(dst, src);
+
+    const uint32_t indir_size =
+        host->cmd == ETHTOOL_SRSSH &&
+        host->indir_size == ETH_RXFH_INDIR_NO_CHANGE ?
+        0 :
+        host->indir_size;
+    convert_ethtool_rxfh_rss_config(host->rss_config, target->rss_config,
+                                    indir_size, host->key_size);
+}
+
+static int target_ethtool_rxfh_size(const void *src)
+{
+    const struct ethtool_rxfh *target = src;
+    if (tswap32(target->cmd) == ETHTOOL_SRSSH &&
+        tswap32(target->indir_size) == ETH_RXFH_INDIR_NO_CHANGE) {
+        return sizeof(struct ethtool_rxfh) + tswap32(target->key_size);
+    }
+    return sizeof(struct ethtool_rxfh) +
+        tswap32(target->indir_size) * sizeof(target->rss_config[0]) +
+        tswap32(target->key_size);
+}
+
+static int host_ethtool_rxfh_size(const void *src)
+{
+    const struct ethtool_rxfh *host = src;
+    if (host->cmd == ETHTOOL_SRSSH &&
+        host->indir_size == ETH_RXFH_INDIR_NO_CHANGE) {
+        return sizeof(struct ethtool_rxfh) + host->key_size;
+    }
+    return sizeof(struct ethtool_rxfh) +
+        host->indir_size * sizeof(host->rss_config[0]) +
+        host->key_size;
+}
+
+const StructEntry struct_ethtool_rxfh_def = {
+    .convert = {
+        host_to_target_ethtool_rxfh, target_to_host_ethtool_rxfh },
+    .thunk_size = {
+        target_ethtool_rxfh_size, host_ethtool_rxfh_size },
+    .size = {
+        sizeof(struct ethtool_rxfh), sizeof(struct ethtool_rxfh) },
+    .align = {
+        __alignof__(struct ethtool_rxfh), __alignof__(struct ethtool_rxfh) },
+};
+
+/*
+ * struct ethtool_link_settings {
+ *     __u32 cmd;
+ *     __u32 speed;
+ *     __u8  duplex;
+ *     __u8  port;
+ *     __u8  phy_address;
+ *     __u8  autoneg;
+ *     __u8  mdio_support;
+ *     __u8  eth_tp_mdix;
+ *     __u8  eth_tp_mdix_ctrl;
+ *     __s8  link_mode_masks_nwords;
+ *     __u8  transceiver;
+ *     __u8  reserved1[3];
+ *     __u32 reserved[7];
+ *     __u32 link_mode_masks[0];
+ * };
+ *
+ * layout of link_mode_masks fields:
+ * __u32 map_supported[link_mode_masks_nwords];
+ * __u32 map_advertising[link_mode_masks_nwords];
+ * __u32 map_lp_advertising[link_mode_masks_nwords];
+ *
+ * `link_mode_masks_nwords` can be negative when returning from kernel if the
+ * provided request size is not supported.
+ */
+
+static void host_to_target_ethtool_link_settings(void *dst, const void *src)
+{
+    int i;
+    struct ethtool_link_settings *target = dst;
+    const struct ethtool_link_settings *host = src;
+
+    target->cmd = tswap32(host->cmd);
+    target->speed = tswap32(host->speed);
+    target->duplex = host->duplex;
+    target->port = host->port;
+    target->phy_address = host->phy_address;
+    target->autoneg = host->autoneg;
+    target->mdio_support = host->mdio_support;
+    target->eth_tp_mdix = host->eth_tp_mdix;
+    target->eth_tp_mdix_ctrl = host->eth_tp_mdix_ctrl;
+    target->link_mode_masks_nwords = host->link_mode_masks_nwords;
+    target->transceiver = host->transceiver;
+    for (i = 0; i < 3; ++i) {
+        target->reserved1[i] = host->reserved1[i];
+    }
+    for (i = 0; i < 7; ++i) {
+        target->reserved[i] = tswap32(host->reserved[i]);
+    }
+
+    if (host->link_mode_masks_nwords > 0) {
+        for (i = 0; i < host->link_mode_masks_nwords * 3; ++i) {
+            target->link_mode_masks[i] = tswap32(host->link_mode_masks[i]);
+        }
+    }
+}
+
+static void target_to_host_ethtool_link_settings(void *dst, const void *src)
+{
+    int i;
+    struct ethtool_link_settings *host = dst;
+    const struct ethtool_link_settings *target = src;
+
+    host->cmd = tswap32(target->cmd);
+    host->speed = tswap32(target->speed);
+    host->duplex = target->duplex;
+    host->port = target->port;
+    host->phy_address = target->phy_address;
+    host->autoneg = target->autoneg;
+    host->mdio_support = target->mdio_support;
+    host->eth_tp_mdix = target->eth_tp_mdix;
+    host->eth_tp_mdix_ctrl = target->eth_tp_mdix_ctrl;
+    host->link_mode_masks_nwords = target->link_mode_masks_nwords;
+    host->transceiver = target->transceiver;
+    for (i = 0; i < 3; ++i) {
+        host->reserved1[i] = target->reserved1[i];
+    }
+    for (i = 0; i < 7; ++i) {
+        host->reserved[i] = tswap32(target->reserved[i]);
+    }
+
+    if (host->link_mode_masks_nwords > 0) {
+        for (i = 0; i < host->link_mode_masks_nwords * 3; ++i) {
+            host->link_mode_masks[i] = tswap32(target->link_mode_masks[i]);
+        }
+    }
+}
+
+static int target_ethtool_link_settings_size(const void *src)
+{
+    const struct ethtool_link_settings *target = src;
+    if (target->link_mode_masks_nwords > 0) {
+        return sizeof(struct ethtool_link_settings) +
+            3 * target->link_mode_masks_nwords *
+            sizeof(target->link_mode_masks[0]);
+    } else {
+        return sizeof(struct ethtool_link_settings);
+    }
+}
+
+static int host_ethtool_link_settings_size(const void *src)
+{
+    const struct ethtool_link_settings *host = src;
+    if (host->link_mode_masks_nwords > 0) {
+        return sizeof(struct ethtool_link_settings) +
+            3 * host->link_mode_masks_nwords *
+            sizeof(host->link_mode_masks[0]);
+    } else {
+        return sizeof(struct ethtool_link_settings);
+    }
+}
+
+const StructEntry struct_ethtool_link_settings_def = {
+    .convert = {
+        host_to_target_ethtool_link_settings,
+        target_to_host_ethtool_link_settings
+    },
+    .thunk_size = {
+        target_ethtool_link_settings_size, host_ethtool_link_settings_size },
+    .size = {
+        sizeof(struct ethtool_link_settings),
+        sizeof(struct ethtool_link_settings) },
+    .align = {
+        __alignof__(struct ethtool_link_settings),
+        __alignof__(struct ethtool_link_settings) },
+};
+
+/*
+ * struct ethtool_per_queue_op {
+ *     __u32 cmd;
+ *     __u32 sub_command;
+ *     __u32 queue_mask[__KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32)];
+ *     char  data[];
+ * };
+ *
+ * `queue_mask` are a series of bitmasks of the queues. `data` is a complete
+ * command structure for each of the queues addressed.
+ *
+ * When `cmd` is `ETHTOOL_PERQUEUE` and `sub_command` is `ETHTOOL_GCOALESCE` or
+ * `ETHTOOL_SCOALESCE`, the command structure is `struct ethtool_coalesce`.
+ */
+static void host_to_target_ethtool_per_queue_op(void *dst, const void *src)
+{
+    static const argtype ethtool_coalesce_argtype[] = {
+        MK_STRUCT(STRUCT_ethtool_coalesce), TYPE_NULL };
+    int i, queue_count;
+    struct ethtool_per_queue_op *target = dst;
+    const struct ethtool_per_queue_op *host = src;
+
+    target->cmd = tswap32(host->cmd);
+    target->sub_command = tswap32(host->sub_command);
+
+    queue_count = 0;
+    for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) {
+        target->queue_mask[i] = tswap32(host->queue_mask[i]);
+        queue_count += ctpop32(host->queue_mask[i]);
+    }
+
+    if (host->cmd != ETHTOOL_PERQUEUE ||
+        (host->sub_command != ETHTOOL_GCOALESCE &&
+         host->sub_command != ETHTOOL_SCOALESCE)) {
+        fprintf(stderr,
+                "Unknown command 0x%x sub_command 0x%x for "
+                "ethtool_per_queue_op, unable to convert the `data` field "
+                "(host-to-target)\n",
+                host->cmd, host->sub_command);
+        return;
+    }
+
+    for (i = 0; i < queue_count; ++i) {
+        thunk_convert(target->data + i * sizeof(struct ethtool_coalesce),
+                      host->data + i * sizeof(struct ethtool_coalesce),
+                      ethtool_coalesce_argtype, THUNK_TARGET);
+    }
+}
+
+static void target_to_host_ethtool_per_queue_op(void *dst, const void *src)
+{
+    static const argtype ethtool_coalesce_argtype[] = {
+        MK_STRUCT(STRUCT_ethtool_coalesce), TYPE_NULL };
+    int i, queue_count;
+    struct ethtool_per_queue_op *host = dst;
+    const struct ethtool_per_queue_op *target = src;
+
+    host->cmd = tswap32(target->cmd);
+    host->sub_command = tswap32(target->sub_command);
+
+    queue_count = 0;
+    for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) {
+        host->queue_mask[i] = tswap32(target->queue_mask[i]);
+        queue_count += ctpop32(host->queue_mask[i]);
+    }
+
+    if (host->cmd != ETHTOOL_PERQUEUE ||
+        (host->sub_command != ETHTOOL_GCOALESCE &&
+         host->sub_command != ETHTOOL_SCOALESCE)) {
+        fprintf(stderr,
+                "Unknown command 0x%x sub_command 0x%x for "
+                "ethtool_per_queue_op, unable to convert the `data` field "
+                "(target-to-host)\n",
+                host->cmd, host->sub_command);
+        return;
+    }
+
+    for (i = 0; i < queue_count; ++i) {
+        thunk_convert(host->data + i * sizeof(struct ethtool_coalesce),
+                      target->data + i * sizeof(struct ethtool_coalesce),
+                      ethtool_coalesce_argtype, THUNK_HOST);
+    }
+}
+
+static int target_ethtool_per_queue_op_size(const void *src)
+{
+    int i, queue_count;
+    const struct ethtool_per_queue_op *target = src;
+
+    if (tswap32(target->cmd) != ETHTOOL_PERQUEUE ||
+        (tswap32(target->sub_command) != ETHTOOL_GCOALESCE &&
+         tswap32(target->sub_command) != ETHTOOL_SCOALESCE)) {
+        fprintf(stderr,
+                "Unknown command 0x%x sub_command 0x%x for "
+                "ethtool_per_queue_op, unable to compute the size of the "
+                "`data` field (target)\n",
+                tswap32(target->cmd), tswap32(target->sub_command));
+        return sizeof(struct ethtool_per_queue_op);
+    }
+
+    queue_count = 0;
+    for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) {
+        queue_count += ctpop32(target->queue_mask[i]);
+    }
+    return sizeof(struct ethtool_per_queue_op) +
+        queue_count * sizeof(struct ethtool_coalesce);
+}
+
+static int host_ethtool_per_queue_op_size(const void *src)
+{
+    int i, queue_count;
+    const struct ethtool_per_queue_op *host = src;
+
+    if (host->cmd != ETHTOOL_PERQUEUE ||
+        (host->sub_command != ETHTOOL_GCOALESCE &&
+         host->sub_command != ETHTOOL_SCOALESCE)) {
+        fprintf(stderr,
+                "Unknown command 0x%x sub_command 0x%x for "
+                "ethtool_per_queue_op, unable to compute the size of the "
+                "`data` field (host)\n",
+                host->cmd, host->sub_command);
+        return sizeof(struct ethtool_per_queue_op);
+    }
+
+    queue_count = 0;
+    for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) {
+        queue_count += ctpop32(host->queue_mask[i]);
+    }
+    return sizeof(struct ethtool_per_queue_op) +
+        queue_count * sizeof(struct ethtool_coalesce);
+}
+
+const StructEntry struct_ethtool_per_queue_op_def = {
+    .convert = {
+        host_to_target_ethtool_per_queue_op,
+        target_to_host_ethtool_per_queue_op
+    },
+    .thunk_size = {
+        target_ethtool_per_queue_op_size, host_ethtool_per_queue_op_size },
+    .size = {
+        sizeof(struct ethtool_per_queue_op),
+        sizeof(struct ethtool_per_queue_op) },
+    .align = {
+        __alignof__(struct ethtool_per_queue_op),
+        __alignof__(struct ethtool_per_queue_op) },
+};
+
+#define safe_dev_ethtool(fd, ...) \
+    safe_syscall(__NR_ioctl, (fd), SIOCETHTOOL, __VA_ARGS__)
+
+typedef struct EthtoolEntry EthtoolEntry;
+
+typedef abi_long do_ethtool_fn(const EthtoolEntry *ee, uint8_t *buf_temp,
+                               int fd, struct ifreq *host_ifreq);
+
+struct EthtoolEntry {
+    uint32_t cmd;
+    int access;
+    do_ethtool_fn *do_ethtool;
+    const argtype arg_type[3];
+};
+
+#define ETHT_R 0x0001
+#define ETHT_W 0x0002
+#define ETHT_RW (ETHT_R | ETHT_W)
+
+static do_ethtool_fn do_ethtool_get_rxfh;
+
+static EthtoolEntry ethtool_entries[] = {
+#define ETHTOOL(cmd, access, ...) \
+    { cmd, access, 0, { __VA_ARGS__ } },
+#define ETHTOOL_SPECIAL(cmd, access, dofn, ...) \
+    { cmd, access, dofn, { __VA_ARGS__ } },
+#include "ethtool_entries.h"
+#undef ETHTOOL
+#undef ETHTOOL_SPECIAL
+    { 0, 0 },
+};
+
+/*
+ * ETHTOOL_GRSSH has two modes of operations: querying the sizes of the indir
+ * and key as well as actually querying the indir and key. When either
+ * `indir_size` or `key_size` is zero, the size of the corresponding entry is
+ * retrieved and updated into the `ethtool_rxfh` struct. When either of them is
+ * non-zero, the actually indir or key is written to `rss_config`.
+ *
+ * This causes a problem for the generic framework which converts between host
+ * and target structures without the context. When the convertion function sees
+ * an `ethtool_rxfh` struct with non-zero `indir_size` or `key_size`, it has to
+ * assume that there are entries in `rss_config` and needs to convert them.
+ * Unfortunately, when converting the returned `ethtool_rxfh` struct from host
+ * to target after an ETHTOOL_GRSSH call with the first mode, the `indir_size`
+ * and `key_size` fields are populated but there is no actual data to be
+ * converted. More importantly, user programs would not have prepared enough
+ * memory for the convertion to take place safely.
+ *
+ * ETHTOOL_GRSSH thus needs a special implementation which is aware of the two
+ * modes of operations and converts the structure accordingly.
+ */
+abi_long do_ethtool_get_rxfh(const EthtoolEntry *ee, uint8_t *buf_temp,
+                             int fd, struct ifreq *host_ifreq)
+{
+    const argtype *arg_type = ee->arg_type;
+    const abi_long ifreq_data = (abi_long)(unsigned long)host_ifreq->ifr_data;
+    struct ethtool_rxfh *rxfh = (struct ethtool_rxfh *)buf_temp;
+    uint32_t user_indir_size, user_key_size;
+    abi_long ret;
+    void *argptr;
+
+    assert(arg_type[0] == TYPE_PTR);
+    assert(ee->access == IOC_RW);
+    arg_type++;
+
+    /*
+     * As of Linux kernel v5.8-rc4, ETHTOOL_GRSSH calls never read the
+     * `rss_config` part. Converting only the "header" part suffices.
+     */
+    argptr = lock_user(VERIFY_READ, ifreq_data, sizeof(*rxfh), 1);
+    if (!argptr) {
+        return -TARGET_EFAULT;
+    }
+    convert_ethtool_rxfh_header(rxfh, argptr);
+    unlock_user(argptr, ifreq_data, sizeof(*rxfh));
+
+    if (rxfh->cmd != ETHTOOL_GRSSH) {
+        return -TARGET_EINVAL;
+    }
+    user_indir_size = rxfh->indir_size;
+    user_key_size = rxfh->key_size;
+
+    host_ifreq->ifr_data = (void *)rxfh;
+    ret = get_errno(safe_dev_ethtool(fd, host_ifreq));
+
+    /*
+     * When a user program supplies `indir_size` or `key_size` but does not
+     * match what the kernel has, the syscall returns EINVAL but the structure
+     * is already updated. Mimicking it here.
+     */
+    argptr = lock_user(VERIFY_WRITE, ifreq_data, sizeof(*rxfh), 0);
+    if (!argptr) {
+        return -TARGET_EFAULT;
+    }
+    convert_ethtool_rxfh_header(argptr, rxfh);
+    unlock_user(argptr, ifreq_data, 0);
+
+    if (is_error(ret)) {
+        return ret;
+    }
+
+    if (user_indir_size > 0 || user_key_size > 0) {
+        const int rss_config_size =
+            user_indir_size * sizeof(rxfh->rss_config[0]) + user_key_size;
+        argptr = lock_user(VERIFY_WRITE, ifreq_data + sizeof(*rxfh),
+                           rss_config_size, 0);
+        if (!argptr) {
+            return -TARGET_EFAULT;
+        }
+        convert_ethtool_rxfh_rss_config(argptr, rxfh->rss_config,
+                                        user_indir_size, user_key_size);
+        unlock_user(argptr, ifreq_data + sizeof(*rxfh), rss_config_size);
+    }
+    return ret;
+}
+
+/*
+ * Calculates the size of the data type represented by `type_ptr` with
+ * `guest_addr` being the underlying memory. Since `type_ptr` may contain
+ * flexible arrays, we need access to the underlying memory to determine their
+ * sizes.
+ */
+static int thunk_size(abi_long guest_addr, const argtype *type_ptr)
+{
+    /*
+     * lock_user based on `thunk_type_size` then call `thunk_type_size_with_src`
+     * on it.
+     */
+    void *src;
+    int type_size = thunk_type_size(type_ptr, /*is_host=*/ 0);
+    if (!thunk_type_has_flexible_array(type_ptr)) {
+        return type_size;
+    }
+
+    src = lock_user(VERIFY_READ, guest_addr, type_size, 0);
+    type_size = thunk_type_size_with_src(src, type_ptr, /*is_host=*/ 0);
+    unlock_user(src, guest_addr, 0);
+
+    return type_size;
+}
+
+abi_long dev_ethtool(int fd, uint8_t *buf_temp)
+{
+    uint32_t *cmd;
+    uint32_t host_cmd;
+    const EthtoolEntry *ee;
+    const argtype *arg_type;
+    abi_long ret;
+    int target_size;
+    void *argptr;
+
+    /*
+     * Make a copy of `host_ifreq` because we are going to reuse `buf_temp` and
+     * overwrite it. Further, we will overwrite `host_ifreq.ifreq_data`, so
+     * keep a copy in `ifreq_data`.
+     */
+    struct ifreq host_ifreq = *(struct ifreq *)(unsigned long)buf_temp;
+    const abi_long ifreq_data = (abi_long)(unsigned long)host_ifreq.ifr_data;
+
+    cmd = (uint32_t *)lock_user(VERIFY_READ, ifreq_data, sizeof(uint32_t), 0);
+    host_cmd = tswap32(*cmd);
+    unlock_user(cmd, ifreq_data, 0);
+
+    ee = ethtool_entries;
+    for (;;) {
+        if (ee->cmd == 0) {
+            qemu_log_mask(LOG_UNIMP, "Unsupported ethtool cmd=0x%04lx\n",
+                          (long)host_cmd);
+            return -TARGET_ENOSYS;
+        }
+        if (ee->cmd == host_cmd) {
+            break;
+        }
+        ee++;
+    }
+    if (ee->do_ethtool) {
+        return ee->do_ethtool(ee, buf_temp, fd, &host_ifreq);
+    }
+
+    host_ifreq.ifr_data = buf_temp;
+    /* Even for ETHT_R, cmd still needs to be copied. */
+    *(uint32_t *)buf_temp = host_cmd;
+
+    arg_type = ee->arg_type;
+    switch (arg_type[0]) {
+    case TYPE_NULL:
+        /* no argument other than cmd */
+        ret = get_errno(safe_dev_ethtool(fd, &host_ifreq));
+        break;
+    case TYPE_PTR:
+        arg_type++;
+        target_size = thunk_size(ifreq_data, arg_type);
+        switch (ee->access) {
+        case ETHT_R:
+            ret = get_errno(safe_dev_ethtool(fd, &host_ifreq));
+            if (!is_error(ret)) {
+                argptr = lock_user(VERIFY_WRITE, ifreq_data, target_size, 0);
+                if (!argptr) {
+                    return -TARGET_EFAULT;
+                }
+                thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET);
+                unlock_user(argptr, ifreq_data, target_size);
+            }
+            break;
+        case ETHT_W:
+            argptr = lock_user(VERIFY_READ, ifreq_data, target_size, 1);
+            if (!argptr) {
+                return -TARGET_EFAULT;
+            }
+            thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST);
+            unlock_user(argptr, ifreq_data, 0);
+            ret = get_errno(safe_dev_ethtool(fd, &host_ifreq));
+            break;
+        default:
+        case ETHT_RW:
+            argptr = lock_user(VERIFY_READ, ifreq_data, target_size, 1);
+            if (!argptr) {
+                return -TARGET_EFAULT;
+            }
+            thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST);
+            unlock_user(argptr, ifreq_data, 0);
+            ret = get_errno(safe_dev_ethtool(fd, &host_ifreq));
+            if (!is_error(ret)) {
+                argptr = lock_user(VERIFY_WRITE, ifreq_data, target_size, 0);
+                if (!argptr) {
+                    return -TARGET_EFAULT;
+                }
+                thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET);
+                unlock_user(argptr, ifreq_data, target_size);
+            }
+            break;
+        }
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP,
+                      "Unsupported ethtool type: cmd=0x%04lx type=%d\n",
+                      (long)host_cmd, arg_type[0]);
+        ret = -TARGET_ENOSYS;
+        break;
+    }
+    return ret;
+}
diff --git a/linux-user/ethtool.h b/linux-user/ethtool.h
new file mode 100644
index 0000000000..6942aef095
--- /dev/null
+++ b/linux-user/ethtool.h
@@ -0,0 +1,20 @@
+#ifndef ETHTOOL_H
+#define ETHTOOL_H
+
+#include <linux/if.h>
+#include "qemu.h"
+
+extern const StructEntry struct_ethtool_rxnfc_get_set_rxfh_def;
+extern const StructEntry struct_ethtool_sset_info_def;
+extern const StructEntry struct_ethtool_rxfh_def;
+extern const StructEntry struct_ethtool_link_settings_def;
+extern const StructEntry struct_ethtool_per_queue_op_def;
+
+/*
+ * Takes the file descriptor and the buffer for temporarily storing data read
+ * from / to be written to guest memory. `buf_temp` must now contain the host
+ * representation of `struct ifreq`.
+ */
+abi_long dev_ethtool(int fd, uint8_t *buf_temp);
+
+#endif /* ETHTOOL_H */
diff --git a/linux-user/ethtool_entries.h b/linux-user/ethtool_entries.h
new file mode 100644
index 0000000000..14f4e80a21
--- /dev/null
+++ b/linux-user/ethtool_entries.h
@@ -0,0 +1,107 @@
+  ETHTOOL(ETHTOOL_GSET, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_cmd)))
+  ETHTOOL(ETHTOOL_SSET, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_cmd)))
+  ETHTOOL(ETHTOOL_GDRVINFO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_drvinfo)))
+  ETHTOOL(ETHTOOL_GREGS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_regs)))
+  ETHTOOL(ETHTOOL_GWOL, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_wolinfo)))
+  ETHTOOL(ETHTOOL_SWOL, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_wolinfo)))
+  ETHTOOL(ETHTOOL_GMSGLVL, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_SMSGLVL, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GEEE, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_eee)))
+  ETHTOOL(ETHTOOL_SEEE, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_eee)))
+  ETHTOOL(ETHTOOL_NWAY_RST, 0, TYPE_NULL)
+  ETHTOOL(ETHTOOL_GLINK, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GEEPROM, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom)))
+  ETHTOOL(ETHTOOL_SEEPROM, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom)))
+  ETHTOOL(ETHTOOL_GCOALESCE, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_coalesce)))
+  ETHTOOL(ETHTOOL_SCOALESCE, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_coalesce)))
+  ETHTOOL(ETHTOOL_GRINGPARAM, ETHT_R,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_ringparam)))
+  ETHTOOL(ETHTOOL_SRINGPARAM, ETHT_W,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_ringparam)))
+  ETHTOOL(ETHTOOL_GPAUSEPARAM, ETHT_R,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_pauseparam)))
+  ETHTOOL(ETHTOOL_SPAUSEPARAM, ETHT_W,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_pauseparam)))
+  ETHTOOL(ETHTOOL_TEST, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_test)))
+  ETHTOOL(ETHTOOL_GSTRINGS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_gstrings)))
+  ETHTOOL(ETHTOOL_PHYS_ID, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GSTATS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_stats)))
+  ETHTOOL(ETHTOOL_GPERMADDR, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_perm_addr)))
+  ETHTOOL(ETHTOOL_GFLAGS, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_SFLAGS, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GPFLAGS, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_SPFLAGS, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GRXFH, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_get_set_rxfh)))
+  ETHTOOL(ETHTOOL_GRXRINGS, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context)))
+  ETHTOOL(ETHTOOL_GRXCLSRLCNT, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rule_cnt)))
+  ETHTOOL(ETHTOOL_GRXCLSRULE, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context)))
+  ETHTOOL(ETHTOOL_GRXCLSRLALL, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rule_locs)))
+  ETHTOOL(ETHTOOL_SRXFH, ETHT_W,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_get_set_rxfh)))
+  ETHTOOL(ETHTOOL_SRXCLSRLDEL, ETHT_W,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context)))
+  ETHTOOL(ETHTOOL_SRXCLSRLINS, ETHT_W,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context)))
+  ETHTOOL(ETHTOOL_FLASHDEV, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_flash)))
+  ETHTOOL(ETHTOOL_RESET, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GSSET_INFO, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_sset_info)))
+  ETHTOOL(ETHTOOL_GRXFHINDIR, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh_indir)))
+  ETHTOOL(ETHTOOL_SRXFHINDIR, ETHT_W,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh_indir)))
+  ETHTOOL_SPECIAL(ETHTOOL_GRSSH, ETHT_RW, do_ethtool_get_rxfh,
+                  MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh)))
+  ETHTOOL(ETHTOOL_SRSSH, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh)))
+  ETHTOOL(ETHTOOL_GFEATURES, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_gfeatures)))
+  ETHTOOL(ETHTOOL_SFEATURES, ETHT_W,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_sfeatures)))
+  ETHTOOL(ETHTOOL_GTXCSUM, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GRXCSUM, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GSG, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GTSO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GGSO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GGRO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_STXCSUM, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_SRXCSUM, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_SSG, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_STSO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_SGSO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_SGRO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value)))
+  ETHTOOL(ETHTOOL_GCHANNELS, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_channels)))
+  ETHTOOL(ETHTOOL_SCHANNELS, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_channels)))
+  ETHTOOL(ETHTOOL_SET_DUMP, ETHT_W,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_dump_no_data)))
+  ETHTOOL(ETHTOOL_GET_DUMP_FLAG, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_dump_no_data)))
+  ETHTOOL(ETHTOOL_GET_DUMP_DATA, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_dump)))
+  ETHTOOL(ETHTOOL_GET_TS_INFO, ETHT_R,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_ts_info)))
+  ETHTOOL(ETHTOOL_GMODULEINFO, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_modinfo)))
+  ETHTOOL(ETHTOOL_GMODULEEEPROM, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom)))
+  ETHTOOL(ETHTOOL_GTUNABLE, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable)))
+  ETHTOOL(ETHTOOL_STUNABLE, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable)))
+  ETHTOOL(ETHTOOL_GPHYSTATS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_stats)))
+  ETHTOOL(ETHTOOL_PERQUEUE, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_per_queue_op)))
+  ETHTOOL(ETHTOOL_GLINKSETTINGS, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_link_settings)))
+  ETHTOOL(ETHTOOL_SLINKSETTINGS, ETHT_W,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_link_settings)))
+  ETHTOOL(ETHTOOL_PHY_GTUNABLE, ETHT_RW,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable)))
+  ETHTOOL(ETHTOOL_PHY_STUNABLE, ETHT_W,
+          MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable)))
+  ETHTOOL(ETHTOOL_GFECPARAM, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_fecparam)))
+  ETHTOOL(ETHTOOL_GFECPARAM, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_fecparam)))
diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h
index 8efb4d38c0..8bc63b29ed 100644
--- a/linux-user/ioctls.h
+++ b/linux-user/ioctls.h
@@ -362,6 +362,8 @@
   IOCTL(SIOCSIFHWADDR, IOC_W, MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifreq)))
   IOCTL(SIOCGIFTXQLEN, IOC_W | IOC_R, MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifreq)))
   IOCTL(SIOCSIFTXQLEN, IOC_W, MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifreq)))
+  IOCTL_SPECIAL(SIOCETHTOOL, IOC_W | IOC_R, do_ioctl_ethtool,
+                MK_PTR(MK_STRUCT(STRUCT_ptr_ifreq)))
   IOCTL(SIOCGIFMETRIC, IOC_W | IOC_R, MK_PTR(MK_STRUCT(STRUCT_int_ifreq)))
   IOCTL(SIOCSIFMETRIC, IOC_W, MK_PTR(MK_STRUCT(STRUCT_int_ifreq)))
   IOCTL(SIOCGIFMTU, IOC_W | IOC_R, MK_PTR(MK_STRUCT(STRUCT_int_ifreq)))
diff --git a/linux-user/meson.build b/linux-user/meson.build
index 2b94e4ba24..1c62f304cd 100644
--- a/linux-user/meson.build
+++ b/linux-user/meson.build
@@ -1,5 +1,6 @@
 linux_user_ss.add(files(
   'elfload.c',
+  'ethtool.c',
   'exit.c',
   'fd-trans.c',
   'linuxload.c',
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index 534753ca12..754d58965b 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -235,6 +235,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
                     abi_long arg2, abi_long arg3, abi_long arg4,
                     abi_long arg5, abi_long arg6, abi_long arg7,
                     abi_long arg8);
+abi_long get_errno(abi_long ret);
 extern __thread CPUState *thread_cpu;
 void cpu_loop(CPUArchState *env);
 const char *target_strerror(int err);
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 7bf99beb18..50ad08d9e7 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -129,6 +129,7 @@
 #include "qapi/error.h"
 #include "fd-trans.h"
 #include "tcg/tcg.h"
+#include "ethtool.h"
 
 #ifndef CLONE_IO
 #define CLONE_IO                0x80000000      /* Clone io context */
@@ -646,7 +647,7 @@ static inline int target_to_host_errno(int err)
     return err;
 }
 
-static inline abi_long get_errno(abi_long ret)
+abi_long get_errno(abi_long ret)
 {
     if (ret == -1)
         return -host_to_target_errno(errno);
@@ -4778,16 +4779,6 @@ static abi_long do_ipc(CPUArchState *cpu_env,
 #endif
 
 /* kernel structure types definitions */
-
-#define STRUCT(name, ...) STRUCT_ ## name,
-#define STRUCT_SPECIAL(name) STRUCT_ ## name,
-enum {
-#include "syscall_types.h"
-STRUCT_MAX
-};
-#undef STRUCT
-#undef STRUCT_SPECIAL
-
 #define STRUCT(name, ...) static const argtype struct_ ## name ## _def[] = {  __VA_ARGS__, TYPE_NULL };
 #define STRUCT_SPECIAL(name)
 #include "syscall_types.h"
@@ -4885,6 +4876,29 @@ static abi_long do_ioctl_fs_ioc_fiemap(const IOCTLEntry *ie, uint8_t *buf_temp,
 }
 #endif
 
+static abi_long do_ioctl_ethtool(const IOCTLEntry *ie, uint8_t *buf_temp,
+                                int fd, int cmd, abi_long arg)
+{
+    const argtype *arg_type = ie->arg_type;
+    int target_size;
+    void *argptr;
+
+    assert(arg_type[0] == TYPE_PTR);
+    assert(ie->access == IOC_RW);
+
+    arg_type++;
+    target_size = thunk_type_size(arg_type, 0);
+
+    argptr = lock_user(VERIFY_READ, arg, target_size, 1);
+    if (!argptr) {
+        return -TARGET_EFAULT;
+    }
+    thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST);
+    unlock_user(argptr, arg, target_size);
+
+    return dev_ethtool(fd, buf_temp);
+}
+
 static abi_long do_ioctl_ifconf(const IOCTLEntry *ie, uint8_t *buf_temp,
                                 int fd, int cmd, abi_long arg)
 {
diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h
index b934d0b606..ae13f11f6c 100644
--- a/linux-user/syscall_defs.h
+++ b/linux-user/syscall_defs.h
@@ -904,6 +904,8 @@ struct target_rtc_pll_info {
 #define TARGET_SIOCGIFTXQLEN   0x8942          /* Get the tx queue length      */
 #define TARGET_SIOCSIFTXQLEN   0x8943          /* Set the tx queue length      */
 
+#define TARGET_SIOCETHTOOL     0x8946          /* Ethtool interface           */
+
 /* ARP cache control calls. */
 #define TARGET_OLD_SIOCDARP    0x8950          /* old delete ARP table entry   */
 #define TARGET_OLD_SIOCGARP    0x8951          /* old get ARP table entry      */
@@ -2863,4 +2865,14 @@ struct target_statx {
    /* 0x100 */
 };
 
+/* kernel structure types definitions */
+#define STRUCT(name, ...) STRUCT_ ## name,
+#define STRUCT_SPECIAL(name) STRUCT_ ## name,
+enum {
+#include "syscall_types.h"
+STRUCT_MAX
+};
+#undef STRUCT
+#undef STRUCT_SPECIAL
+
 #endif
diff --git a/linux-user/syscall_types.h b/linux-user/syscall_types.h
index ba2c1518eb..f1d374d0e0 100644
--- a/linux-user/syscall_types.h
+++ b/linux-user/syscall_types.h
@@ -1,3 +1,4 @@
+
 STRUCT_SPECIAL(termios)
 
 STRUCT(winsize,
@@ -631,3 +632,282 @@ STRUCT(usbdevfs_disconnect_claim,
         TYPE_INT, /* flags */
         MK_ARRAY(TYPE_CHAR, USBDEVFS_MAXDRIVERNAME + 1)) /* driver */
 #endif /* CONFIG_USBFS */
+
+/* ethtool ioctls */
+STRUCT(ethtool_cmd,
+       TYPE_INT,   /* cmd */
+       TYPE_INT,   /* supported */
+       TYPE_INT,   /* advertising */
+       TYPE_SHORT, /* speed */
+       TYPE_CHAR,  /* duplex */
+       TYPE_CHAR,  /* port */
+       TYPE_CHAR,  /* phy_address */
+       TYPE_CHAR,  /* transceiver */
+       TYPE_CHAR,  /* autoneg */
+       TYPE_CHAR,  /* mdio_support */
+       TYPE_INT,   /* maxtxpkt */
+       TYPE_INT,   /* maxrxpkt */
+       TYPE_SHORT, /* speed_hi */
+       TYPE_CHAR,  /* eth_tp_mdix */
+       TYPE_CHAR,  /* eth_tp_mdix_ctrl */
+       TYPE_INT,   /* lp_advertising */
+       MK_ARRAY(TYPE_INT, 2)) /* reserved */
+
+STRUCT(ethtool_drvinfo,
+       TYPE_INT, /* cmd */
+       MK_ARRAY(TYPE_CHAR, 32), /* driver */
+       MK_ARRAY(TYPE_CHAR, 32), /* version */
+       MK_ARRAY(TYPE_CHAR, 32), /* fw_version[ETHTOOL_FWVERS_LEN] */
+       MK_ARRAY(TYPE_CHAR, 32), /* bus_info[ETHTOOL_BUSINFO_LEN] */
+       MK_ARRAY(TYPE_CHAR, 32), /* erom_version[ETHTOOL_EROMVERS_LEN] */
+       MK_ARRAY(TYPE_CHAR, 12), /* reserved2 */
+       TYPE_INT, /* n_priv_flags */
+       TYPE_INT, /* n_stats */
+       TYPE_INT, /* testinfo_len */
+       TYPE_INT, /* eedump_len */
+       TYPE_INT) /* regdump_len */
+
+STRUCT(ethtool_regs,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* version */
+       TYPE_INT, /* len */
+       MK_FLEXIBLE_ARRAY(TYPE_CHAR, 2)) /* data[0]: len */
+
+STRUCT(ethtool_wolinfo,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* supported */
+       TYPE_INT, /* wolopts */
+       MK_ARRAY(TYPE_CHAR,  6)) /* sopass[SOPASS_MAX] */
+
+STRUCT(ethtool_value,
+       TYPE_INT, /* cmd */
+       TYPE_INT) /* data */
+
+STRUCT(ethtool_eee,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* supported */
+       TYPE_INT, /* advertised */
+       TYPE_INT, /* lp_advertised */
+       TYPE_INT, /* eee_active */
+       TYPE_INT, /* eee_enabled */
+       TYPE_INT, /* tx_lpi_enabled */
+       TYPE_INT, /* tx_lpi_timer */
+       MK_ARRAY(TYPE_INT, 2)) /* reserved */
+
+STRUCT(ethtool_eeprom,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* magic */
+       TYPE_INT, /* offset */
+       TYPE_INT, /* len */
+       MK_FLEXIBLE_ARRAY(TYPE_CHAR, 3)) /* data[0]: len */
+
+STRUCT(ethtool_coalesce,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* rx_coalesce_usecs */
+       TYPE_INT, /* rx_max_coalesced_frames */
+       TYPE_INT, /* rx_coalesce_usecs_irq */
+       TYPE_INT, /* rx_max_coalesced_frames_irq */
+       TYPE_INT, /* tx_coalesce_usecs */
+       TYPE_INT, /* tx_max_coalesced_frames */
+       TYPE_INT, /* tx_coalesce_usecs_irq */
+       TYPE_INT, /* tx_max_coalesced_frames_irq */
+       TYPE_INT, /* stats_block_coalesce_usecs */
+       TYPE_INT, /* use_adaptive_rx_coalesce */
+       TYPE_INT, /* use_adaptive_tx_coalesce */
+       TYPE_INT, /* pkt_rate_low */
+       TYPE_INT, /* rx_coalesce_usecs_low */
+       TYPE_INT, /* rx_max_coalesced_frames_low */
+       TYPE_INT, /* tx_coalesce_usecs_low */
+       TYPE_INT, /* tx_max_coalesced_frames_low */
+       TYPE_INT, /* pkt_rate_high */
+       TYPE_INT, /* rx_coalesce_usecs_high */
+       TYPE_INT, /* rx_max_coalesced_frames_high */
+       TYPE_INT, /* tx_coalesce_usecs_high */
+       TYPE_INT, /* tx_max_coalesced_frames_high */
+       TYPE_INT) /* rate_sample_interval */
+
+STRUCT(ethtool_ringparam,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* rx_max_pending */
+       TYPE_INT, /* rx_mini_max_pending */
+       TYPE_INT, /* rx_jumbo_max_pending */
+       TYPE_INT, /* tx_max_pending */
+       TYPE_INT, /* rx_pending */
+       TYPE_INT, /* rx_mini_pending */
+       TYPE_INT, /* rx_jumbo_pending */
+       TYPE_INT) /* tx_pending */
+
+STRUCT(ethtool_pauseparam,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* autoneg */
+       TYPE_INT, /* rx_pause */
+       TYPE_INT) /* tx_pause */
+
+STRUCT(ethtool_test,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* flags */
+       TYPE_INT, /* reserved */
+       TYPE_INT, /* len */
+       MK_FLEXIBLE_ARRAY(TYPE_LONGLONG, 3)) /* data[0]: len */
+
+STRUCT(ethtool_gstrings,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* string_set */
+       TYPE_INT, /* len */
+       MK_FLEXIBLE_ARRAY(MK_ARRAY(TYPE_CHAR, 32), 2))
+       /* data[0]: len * ETH_GSTRING_LEN */
+
+STRUCT(ethtool_stats,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* n_stats */
+       MK_FLEXIBLE_ARRAY(TYPE_LONGLONG, 1)) /* data[0]: n_stats */
+
+STRUCT(ethtool_perm_addr,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* size */
+       MK_FLEXIBLE_ARRAY(TYPE_CHAR, 1)) /* data[0]: size */
+
+STRUCT(ethtool_flow_ext,
+       MK_ARRAY(TYPE_CHAR, 2), /* padding */
+       MK_ARRAY(TYPE_CHAR, 6), /* h_dest[ETH_ALEN] */
+       MK_ARRAY(TYPE_CHAR, 2), /* __be16 vlan_etype */
+       MK_ARRAY(TYPE_CHAR, 2), /* __be16 vlan_tci */
+       MK_ARRAY(TYPE_CHAR, 8)) /* __be32 data[2] */
+
+/*
+ * Union ethtool_flow_union contains alternatives that are either struct that
+ * only uses __be* types or char/__u8, or "__u8 hdata[52]". We can treat it as
+ * byte array in all cases.
+ */
+STRUCT(ethtool_rx_flow_spec,
+       TYPE_INT,                           /* flow_type */
+       MK_ARRAY(TYPE_CHAR, 52),            /* union ethtool_flow_union h_u */
+       MK_STRUCT(STRUCT_ethtool_flow_ext), /* h_ext */
+       MK_ARRAY(TYPE_CHAR, 52),            /* union ethtool_flow_union m_u */
+       MK_STRUCT(STRUCT_ethtool_flow_ext), /* m_ext */
+       TYPE_LONGLONG,                      /* ring_cookie */
+       TYPE_INT)                           /* location */
+
+STRUCT(ethtool_rxnfc_rss_context,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* flow_type */
+       TYPE_LONGLONG, /* data */
+       MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */
+       TYPE_INT) /* rss_context */
+
+STRUCT(ethtool_rxnfc_rule_cnt,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* flow_type */
+       TYPE_LONGLONG, /* data */
+       MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */
+       TYPE_INT) /* rss_cnt */
+
+STRUCT(ethtool_rxnfc_rule_locs,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* flow_type */
+       TYPE_LONGLONG, /* data */
+       MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */
+       TYPE_INT, /* rss_cnt */
+       MK_FLEXIBLE_ARRAY(TYPE_INT, 4)) /* rule_locs[0]: rss_cnt */
+
+/*
+ * For ETHTOOL_{G,S}RXFH, originally only the first three fields are defined,
+ * but with certain options, more fields are used.
+ */
+STRUCT_SPECIAL(ethtool_rxnfc_get_set_rxfh)
+
+STRUCT(ethtool_flash,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* region */
+       MK_ARRAY(TYPE_CHAR, 128)) /* data[ETHTOOL_FLASH_MAX_FILENAME] */
+
+STRUCT_SPECIAL(ethtool_sset_info)
+
+STRUCT(ethtool_rxfh_indir,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* size */
+       MK_FLEXIBLE_ARRAY(TYPE_INT, 1)) /* ring_index[0]: size */
+
+STRUCT_SPECIAL(ethtool_rxfh)
+
+STRUCT(ethtool_get_features_block,
+       TYPE_INT, /* available */
+       TYPE_INT, /* requested */
+       TYPE_INT, /* active */
+       TYPE_INT) /* never_changed */
+
+STRUCT(ethtool_gfeatures,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* size */
+       MK_FLEXIBLE_ARRAY(MK_STRUCT(STRUCT_ethtool_get_features_block), 1))
+       /* features[0]: size */
+
+STRUCT(ethtool_set_features_block,
+       TYPE_INT, /* valid */
+       TYPE_INT) /* requested */
+
+STRUCT(ethtool_sfeatures,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* size */
+       MK_FLEXIBLE_ARRAY(MK_STRUCT(STRUCT_ethtool_set_features_block), 1))
+       /* features[0]: size */
+
+STRUCT(ethtool_channels,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* max_rx */
+       TYPE_INT, /* max_tx */
+       TYPE_INT, /* max_other */
+       TYPE_INT, /* max_combined */
+       TYPE_INT, /* rx_count */
+       TYPE_INT, /* tx_count */
+       TYPE_INT, /* other_count */
+       TYPE_INT) /* combined_count */
+
+/*
+ * For ETHTOOL_SET_DUMP and ETHTOOL_GET_DUMP_FLAG, the flexible array `data` is
+ * not used.
+ */
+STRUCT(ethtool_dump_no_data,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* version */
+       TYPE_INT, /* flag */
+       TYPE_INT) /* len */
+
+STRUCT(ethtool_dump,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* version */
+       TYPE_INT, /* flag */
+       TYPE_INT, /* len */
+       MK_FLEXIBLE_ARRAY(TYPE_CHAR, 3)) /* data[0]: len */
+
+STRUCT(ethtool_ts_info,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* so_timestamping */
+       TYPE_INT, /* phc_index */
+       TYPE_INT, /* tx_types */
+       MK_ARRAY(TYPE_INT, 3), /* tx_reserved */
+       TYPE_INT, /* rx_filters */
+       MK_ARRAY(TYPE_INT, 3)) /* rx_reserved */
+
+STRUCT(ethtool_modinfo,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* type */
+       TYPE_INT, /* eeprom_len */
+       MK_ARRAY(TYPE_INT, 8)) /* reserved */
+
+STRUCT(ethtool_tunable,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* id */
+       TYPE_INT, /* type_id */
+       TYPE_INT, /* len */
+       MK_FLEXIBLE_ARRAY(TYPE_PTRVOID, 3)) /* data[0]: len */
+
+STRUCT_SPECIAL(ethtool_link_settings)
+
+STRUCT(ethtool_fecparam,
+       TYPE_INT, /* cmd */
+       TYPE_INT, /* active_fec */
+       TYPE_INT, /* fec */
+       TYPE_INT) /* reserved */
+
+STRUCT_SPECIAL(ethtool_per_queue_op)
diff --git a/tests/tcg/multiarch/ethtool.c b/tests/tcg/multiarch/ethtool.c
new file mode 100644
index 0000000000..dcb10230e0
--- /dev/null
+++ b/tests/tcg/multiarch/ethtool.c
@@ -0,0 +1,423 @@
+#include <asm-generic/errno.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <linux/ethtool.h>
+#include <linux/if.h>
+#include <linux/sockios.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+const int number_of_entries_to_print = 10;
+const uint32_t protected_memory_pattern[] = {
+      0xdeadc0de, 0xb0bb1e, 0xfacade, 0xfeeb1e };
+
+static void fail_with(const char *action, const char *cmd_name, int cmd,
+                      int err)
+{
+    if (errno == EOPNOTSUPP) {
+        printf("Unsupported operation: %s; errno = %d: %s.\n"
+               "TEST SKIPPED (%s = 0x%x).\n",
+               action, err, strerror(err), cmd_name, cmd);
+        return;
+    }
+    if (err) {
+        fprintf(stderr,
+                "Failed to %s (%s = 0x%x): errno = %d: %s\n",
+                action, cmd_name, cmd, err, strerror(err));
+    } else {
+        fprintf(stderr,
+                "Failed to %s (%s = 0x%x): no errno\n",
+                action, cmd_name, cmd);
+    }
+    exit(err);
+}
+#define FAIL(action, cmd) fail_with(action, #cmd, cmd, errno)
+
+/*
+ * `calloc_protected` and `protected_memory_changed` can be used to verify that
+ * a system call does not write pass intended memory boundary.
+ *
+ * `ptr = calloc_protected(n)` will allocate extra memory after `n` bytes and
+ * populate it with a memory pattern. The first `n` bytes are still guaranteed
+ * to be zeroed out like `calloc(1, n)`. `protected_memory_changed(ptr, n)`
+ * takes the pointer and the original size `n` and checks that the memory
+ * pattern is intact.
+ */
+uint8_t *calloc_protected(size_t struct_size)
+{
+    uint8_t *buf = (uint8_t *) calloc(
+        1,
+        struct_size + sizeof(protected_memory_pattern));
+    memcpy(buf + struct_size, protected_memory_pattern,
+           sizeof(protected_memory_pattern));
+    return buf;
+}
+
+bool protected_memory_changed(const uint8_t *ptr, size_t struct_size)
+{
+    return memcmp(ptr + struct_size, protected_memory_pattern,
+                  sizeof(protected_memory_pattern)) != 0;
+}
+
+void print_entries(const char *fmt, int len, uint32_t *entries)
+{
+    int i;
+    for (i = 0; i < len && i < number_of_entries_to_print; ++i) {
+        printf(fmt, entries[i]);
+    }
+    if (len > number_of_entries_to_print) {
+        printf(" (%d more omitted)", len - number_of_entries_to_print);
+    }
+}
+
+void basic_test(int socketfd, struct ifreq ifr)
+{
+    struct ethtool_drvinfo drvinfo;
+    drvinfo.cmd = ETHTOOL_GDRVINFO;
+    ifr.ifr_data = (void *)&drvinfo;
+    if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) {
+        FAIL("get driver info", ETHTOOL_GDRVINFO);
+        return;
+    }
+    printf("Driver: %s (version %s)\n", drvinfo.driver, drvinfo.version);
+}
+
+/* Test flexible array. */
+void test_get_stats(int socketfd, struct ifreq ifr, int n_stats)
+{
+    int i;
+    struct ethtool_stats *stats = (struct ethtool_stats *)calloc(
+        1, sizeof(*stats) + sizeof(stats->data[0]) * n_stats);
+    stats->cmd = ETHTOOL_GSTATS;
+    stats->n_stats = n_stats;
+    ifr.ifr_data = (void *)stats;
+    if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) {
+        FAIL("get statastics", ETHTOOL_GSTATS);
+        free(stats);
+        return;
+    }
+    if (stats->n_stats != n_stats) {
+        FAIL("get consistent number of statistics", ETHTOOL_GSTATS);
+    }
+    for (i = 0; i < stats->n_stats && i < number_of_entries_to_print; ++i) {
+        printf("stats[%d] = %llu\n", i, (unsigned long long)stats->data[i]);
+    }
+    if (stats->n_stats > number_of_entries_to_print) {
+        printf("(%d more omitted)\n",
+               stats->n_stats - number_of_entries_to_print);
+    }
+    free(stats);
+}
+
+/* Test flexible array with char array as elements. */
+void test_get_strings(int socketfd, struct ifreq ifr, int n_stats)
+{
+    int i;
+    struct ethtool_gstrings *gstrings =
+        (struct ethtool_gstrings *)calloc(
+            1, sizeof(*gstrings) + ETH_GSTRING_LEN * n_stats);
+    gstrings->cmd = ETHTOOL_GSTRINGS;
+    gstrings->string_set = ETH_SS_STATS;
+    gstrings->len = n_stats;
+    ifr.ifr_data = (void *)gstrings;
+    if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) {
+        FAIL("get string set", ETHTOOL_GSTRINGS);
+        free(gstrings);
+        return;
+    }
+    if (gstrings->len != n_stats) {
+        FAIL("get consistent number of statistics", ETHTOOL_GSTRINGS);
+    }
+    for (i = 0; i < gstrings->len && i < number_of_entries_to_print; ++i) {
+        printf("stat_names[%d] = %.*s\n",
+               i, ETH_GSTRING_LEN, gstrings->data + i * ETH_GSTRING_LEN);
+    }
+    if (gstrings->len > number_of_entries_to_print) {
+        printf("(%d more omitted)\n",
+               gstrings->len - number_of_entries_to_print);
+    }
+    free(gstrings);
+}
+
+/*
+ * Testing manual implementation of converting `struct ethtool_sset_info`, also
+ * info for subsequent tests.
+ */
+int test_get_sset_info(int socketfd, struct ifreq ifr)
+{
+    const int n_sset = 2;
+    int n_stats;
+    struct ethtool_sset_info *sset_info =
+        (struct ethtool_sset_info *)calloc(
+            1, sizeof(*sset_info) + sizeof(sset_info->data[0]) * n_sset);
+    sset_info->cmd = ETHTOOL_GSSET_INFO;
+    sset_info->sset_mask = 1 << ETH_SS_TEST | 1 << ETH_SS_STATS;
+    assert(__builtin_popcount(sset_info->sset_mask) == n_sset);
+    ifr.ifr_data = (void *)sset_info;
+    if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) {
+        fail_with("get string set info", "ETHTOOL_GSSET_INFO",
+                  ETHTOOL_GSSET_INFO, errno);
+        free(sset_info);
+        return 0;
+    }
+    if ((sset_info->sset_mask & (1 << ETH_SS_STATS)) == 0) {
+        puts("No stats string set info, SKIPPING dependent tests");
+        free(sset_info);
+        return 0;
+    }
+    n_stats = (sset_info->sset_mask & (1 << ETH_SS_TEST)) ?
+        sset_info->data[1] :
+        sset_info->data[0];
+    printf("n_stats = %d\n", n_stats);
+    free(sset_info);
+    return n_stats;
+}
+
+/*
+ * Test manual implementation of converting `struct ethtool_rxnfc`, focusing on
+ * the case where only the first three fields are present. (The original struct
+ * definition.)
+ */
+void test_get_rxfh(int socketfd, struct ifreq ifr)
+{
+    struct ethtool_rxnfc *rxnfc;
+    const int rxnfc_first_three_field_size =
+        sizeof(rxnfc->cmd) + sizeof(rxnfc->flow_type) + sizeof(rxnfc->data);
+    rxnfc = (struct ethtool_rxnfc *)calloc_protected(
+        rxnfc_first_three_field_size);
+    rxnfc->cmd = ETHTOOL_GRXFH;
+    rxnfc->flow_type = TCP_V4_FLOW;
+    ifr.ifr_data = (void *)rxnfc;
+    if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) {
+        FAIL("get RX flow classification rules", ETHTOOL_GRXFH);
+        free(rxnfc);
+        return;
+    }
+    if (protected_memory_changed((const uint8_t *)rxnfc,
+                                 rxnfc_first_three_field_size)) {
+        FAIL("preserve memory after the first three fields", ETHTOOL_GRXFH);
+    }
+    printf("Flow hash bitmask (flow_type = TCP v4): 0x%llx\n",
+           (unsigned long long)rxnfc->data);
+    free(rxnfc);
+}
+
+/* Test manual implementation of converting `struct ethtool_link_settings`. */
+void test_get_link_settings(int socketfd, struct ifreq ifr)
+{
+    int link_mode_masks_nwords;
+    struct ethtool_link_settings *link_settings_header =
+        (struct ethtool_link_settings *) calloc_protected(
+            sizeof(*link_settings_header));
+    link_settings_header->cmd = ETHTOOL_GLINKSETTINGS;
+    link_settings_header->link_mode_masks_nwords = 0;
+    ifr.ifr_data = (void *)link_settings_header;
+    if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) {
+        FAIL("get link settings mask sizes", ETHTOOL_GLINKSETTINGS);
+        free(link_settings_header);
+        return;
+    }
+    if (protected_memory_changed((const uint8_t *)link_settings_header,
+                                 sizeof(*link_settings_header))) {
+        FAIL("preserve link_mode_masks", ETHTOOL_GLINKSETTINGS);
+    }
+    if (link_settings_header->link_mode_masks_nwords >= 0) {
+        FAIL("complete handshake", ETHTOOL_GLINKSETTINGS);
+    }
+    link_mode_masks_nwords = -link_settings_header->link_mode_masks_nwords;
+
+    struct ethtool_link_settings *link_settings =
+        (struct ethtool_link_settings *)calloc(
+            1,
+            sizeof(*link_settings) +
+            sizeof(link_settings_header->link_mode_masks[0]) *
+            link_mode_masks_nwords * 3);
+    link_settings->cmd = ETHTOOL_GLINKSETTINGS;
+    link_settings->link_mode_masks_nwords = link_mode_masks_nwords;
+    ifr.ifr_data = (void *)link_settings;
+    if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) {
+        FAIL("get link settings", ETHTOOL_GLINKSETTINGS);
+        free(link_settings_header);
+        free(link_settings);
+        return;
+    }
+    if (link_settings->link_mode_masks_nwords != link_mode_masks_nwords) {
+        FAIL("have consistent number of mode masks", ETHTOOL_GLINKSETTINGS);
+    }
+
+    printf("Link speed: %d MB\n", link_settings->speed);
+    printf("Number of link mode masks: %d\n",
+           link_settings->link_mode_masks_nwords);
+    if (link_settings->link_mode_masks_nwords > 0) {
+        printf("Supported bitmap:");
+        print_entries(" 0x%08x",
+                      link_settings->link_mode_masks_nwords,
+                      link_settings->link_mode_masks);
+        putchar('\n');
+
+        printf("Advertising bitmap:");
+        print_entries(" 0x%08x",
+                      link_settings->link_mode_masks_nwords,
+                      link_settings->link_mode_masks +
+                      link_settings->link_mode_masks_nwords);
+        putchar('\n');
+
+        printf("Lp advertising bitmap:");
+        print_entries(" 0x%08x",
+                      link_settings->link_mode_masks_nwords,
+                      link_settings->link_mode_masks +
+                      2 * link_settings->link_mode_masks_nwords);
+        putchar('\n');
+    }
+
+    free(link_settings_header);
+    free(link_settings);
+}
+
+/* Test manual implementation of converting `struct ethtool_per_queue_op`. */
+void test_perqueue(int socketfd, struct ifreq ifr)
+{
+    const int n_queue = 2;
+    int i;
+    struct ethtool_per_queue_op *per_queue_op =
+        (struct ethtool_per_queue_op *)calloc(
+            1,
+            sizeof(*per_queue_op) + sizeof(struct ethtool_coalesce) * n_queue);
+    per_queue_op->cmd = ETHTOOL_PERQUEUE;
+    per_queue_op->sub_command = ETHTOOL_GCOALESCE;
+    per_queue_op->queue_mask[0] = 0x3;
+    ifr.ifr_data = (void *)per_queue_op;
+    if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) {
+        FAIL("get coalesce per queue", ETHTOOL_PERQUEUE);
+        free(per_queue_op);
+        return;
+    }
+    for (i = 0; i < n_queue; ++i) {
+        struct ethtool_coalesce *coalesce = (struct ethtool_coalesce *)(
+            per_queue_op->data + sizeof(*coalesce) * i);
+        if (coalesce->cmd != ETHTOOL_GCOALESCE) {
+            fprintf(stderr,
+                    "ETHTOOL_PERQUEUE (%d) sub_command ETHTOOL_GCOALESCE (%d) "
+                    "fails to set entry %d's cmd to ETHTOOL_GCOALESCE, got %d "
+                    "instead\n",
+                    ETHTOOL_PERQUEUE, ETHTOOL_GCOALESCE, i,
+                    coalesce->cmd);
+            exit(-1);
+        }
+        printf("rx_coalesce_usecs[%d] = %u\nrx_max_coalesced_frames[%d] = %u\n",
+               i, coalesce->rx_coalesce_usecs,
+               i, coalesce->rx_max_coalesced_frames);
+    }
+
+    free(per_queue_op);
+}
+
+/* Test manual implementation of ETHTOOL_GRSSH. */
+void test_get_rssh(int socketfd, struct ifreq ifr)
+{
+    int i;
+    struct ethtool_rxfh *rxfh_header =
+        (struct ethtool_rxfh *)calloc_protected(sizeof(*rxfh_header));
+    rxfh_header->cmd = ETHTOOL_GRSSH;
+    ifr.ifr_data = (void *)rxfh_header;
+    if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) {
+        FAIL("get RX flow hash indir and hash key size", ETHTOOL_GRSSH);
+        free(rxfh_header);
+        return;
+    }
+    if (protected_memory_changed((const uint8_t *)rxfh_header,
+                                 sizeof(*rxfh_header))) {
+        FAIL("preserve rss_config", ETHTOOL_GRSSH);
+    }
+    printf("RX flow hash indir size = %d\nRX flow hash key size = %d\n",
+           rxfh_header->indir_size, rxfh_header->key_size);
+
+    struct ethtool_rxfh *rxfh = (struct ethtool_rxfh *)calloc(
+        1,
+        sizeof(*rxfh) + 4 * rxfh_header->indir_size + rxfh_header->key_size);
+    *rxfh = *rxfh_header;
+    ifr.ifr_data = (void *)rxfh;
+    if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) {
+        FAIL("get RX flow hash indir and hash key", ETHTOOL_GRSSH);
+        free(rxfh_header);
+        free(rxfh);
+        return;
+    }
+
+    if (rxfh->indir_size == 0) {
+        printf("No RX flow hash indir\n");
+    } else {
+        printf("RX flow hash indir:");
+        print_entries(" 0x%08x", rxfh->indir_size, rxfh->rss_config);
+        putchar('\n');
+    }
+
+    if (rxfh->key_size == 0) {
+        printf("No RX flow hash key\n");
+    } else {
+        char *key = (char *)(rxfh->rss_config + rxfh->indir_size);
+        printf("RX flow hash key:");
+        for (i = 0;  i < rxfh->key_size; ++i) {
+            if (i % 2 == 0) {
+                putchar(' ');
+            }
+            printf("%02hhx", key[i]);
+        }
+        putchar('\n');
+    }
+    free(rxfh_header);
+    free(rxfh);
+}
+
+int main(int argc, char **argv)
+{
+    int socketfd, n_stats, i;
+    struct ifreq ifr;
+
+    socketfd = socket(AF_INET, SOCK_DGRAM, 0);
+    if (socketfd == -1) {
+        int err = errno;
+        fprintf(stderr,
+                "Failed to open socket: errno = %d: %s\n",
+                err, strerror(err));
+        return err;
+    }
+
+    for (i = 1;; ++i) {
+        ifr.ifr_ifindex = i;
+        if (ioctl(socketfd, SIOCGIFNAME, &ifr) == -1) {
+            puts("Could not find a non-loopback interface, SKIPPING");
+            return 0;
+        }
+        if (strncmp(ifr.ifr_name, "lo", IFNAMSIZ) != 0) {
+            break;
+        }
+    }
+    printf("Interface index: %d\nInterface name: %.*s\n",
+           ifr.ifr_ifindex, IFNAMSIZ, ifr.ifr_name);
+
+    basic_test(socketfd, ifr);
+
+    n_stats = test_get_sset_info(socketfd, ifr);
+    if (n_stats > 0) {
+        /* Testing lexible arrays. */
+        test_get_stats(socketfd, ifr, n_stats);
+        test_get_strings(socketfd, ifr, n_stats);
+    }
+
+    /* Testing manual implementations of structure convertions. */
+    test_get_rxfh(socketfd, ifr);
+    test_get_link_settings(socketfd, ifr);
+    test_perqueue(socketfd, ifr);
+
+    /* Testing manual implementations of operations. */
+    test_get_rssh(socketfd, ifr);
+
+    return 0;
+}
-- 
2.29.2.684.gfbc64c5ab5-goog



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 0/2] thunk, linux-user: Add support for SIOCETHTOOL ioctl
  2020-12-18 21:41 [PATCH 0/2] thunk, linux-user: Add support for SIOCETHTOOL ioctl Shu-Chun Weng via
  2020-12-18 21:41 ` [PATCH 1/2] thunk: supports flexible arrays Shu-Chun Weng via
  2020-12-18 21:41 ` [PATCH 2/2] linux-user: Add support for SIOCETHTOOL ioctl Shu-Chun Weng via
@ 2020-12-18 21:51 ` no-reply
  2 siblings, 0 replies; 4+ messages in thread
From: no-reply @ 2020-12-18 21:51 UTC (permalink / raw)
  To: qemu-devel; +Cc: laurent, riku.voipio, qemu-devel, scw

Patchew URL: https://patchew.org/QEMU/20201218214142.3673709-1-scw@google.com/



Hi,

This series seems to have some coding style problems. See output below for
more information:

Type: series
Message-id: 20201218214142.3673709-1-scw@google.com
Subject: [PATCH 0/2] thunk, linux-user: Add support for SIOCETHTOOL ioctl

=== TEST SCRIPT BEGIN ===
#!/bin/bash
git rev-parse base > /dev/null || exit 0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
./scripts/checkpatch.pl --mailback base..
=== TEST SCRIPT END ===

Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384
From https://github.com/patchew-project/qemu
 * [new tag]         patchew/20201218214142.3673709-1-scw@google.com -> patchew/20201218214142.3673709-1-scw@google.com
Switched to a new branch 'test'
747a460 linux-user: Add support for SIOCETHTOOL ioctl
c3a717f thunk: supports flexible arrays

=== OUTPUT BEGIN ===
1/2 Checking commit c3a717f27be8 (thunk: supports flexible arrays)
ERROR: Macros with complex values should be enclosed in parenthesis
#52: FILE: include/exec/user/thunk.h:54:
+#define MK_FLEXIBLE_ARRAY(type, len_field_idx) \
+    TYPE_FLEXIBLE_ARRAY, (len_field_idx), type

total: 1 errors, 0 warnings, 266 lines checked

Patch 1/2 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

2/2 Checking commit 747a4602f093 (linux-user: Add support for SIOCETHTOOL ioctl)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#25: 
new file mode 100644

WARNING: Block comments use a leading /* on a separate line
#765: FILE: linux-user/ethtool.c:736:
+    int type_size = thunk_type_size(type_ptr, /*is_host=*/ 0);

WARNING: Block comments use a leading /* on a separate line
#771: FILE: linux-user/ethtool.c:742:
+    type_size = thunk_type_size_with_src(src, type_ptr, /*is_host=*/ 0);

ERROR: Macros with complex values should be enclosed in parenthesis
#1139: FILE: linux-user/syscall_defs.h:2869:
+#define STRUCT(name, ...) STRUCT_ ## name,

ERROR: Macros with complex values should be enclosed in parenthesis
#1140: FILE: linux-user/syscall_defs.h:2870:
+#define STRUCT_SPECIAL(name) STRUCT_ ## name,

total: 2 errors, 3 warnings, 1787 lines checked

Patch 2/2 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

=== OUTPUT END ===

Test command exited with code: 1


The full log is available at
http://patchew.org/logs/20201218214142.3673709-1-scw@google.com/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2020-12-18 22:03 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-12-18 21:41 [PATCH 0/2] thunk, linux-user: Add support for SIOCETHTOOL ioctl Shu-Chun Weng via
2020-12-18 21:41 ` [PATCH 1/2] thunk: supports flexible arrays Shu-Chun Weng via
2020-12-18 21:41 ` [PATCH 2/2] linux-user: Add support for SIOCETHTOOL ioctl Shu-Chun Weng via
2020-12-18 21:51 ` [PATCH 0/2] thunk, " no-reply

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).