Message ID | 61323061160b6d21f43b266764eda813b5e021e2.1597129029.git.scw@google.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | fcntl, sockopt, and ioctl options | expand |
Ping -- any comments on https://patchew.org/QEMU/cover.1597129029.git.scw@google.com/61323061160b6d21f43b266764eda813b5e021e2.1597129029.git.scw@google.com/ On Tue, Aug 11, 2020 at 12:10 AM Shu-Chun Weng <scw@google.com> wrote: > 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> > --- > v1 -> v2: > Fix style problems. > > linux-user/Makefile.objs | 3 +- > linux-user/ethtool.c | 840 ++++++++++++++++++++++++++++++++++ > linux-user/ethtool.h | 20 + > linux-user/ethtool_entries.h | 107 +++++ > linux-user/ioctls.h | 2 + > 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, 1712 insertions(+), 12 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/Makefile.objs b/linux-user/Makefile.objs > index 1940910a73..971d43173a 100644 > --- a/linux-user/Makefile.objs > +++ b/linux-user/Makefile.objs > @@ -1,7 +1,8 @@ > obj-y = main.o syscall.o strace.o mmap.o signal.o \ > elfload.o linuxload.o uaccess.o uname.o \ > safe-syscall.o $(TARGET_ABI_DIR)/signal.o \ > - $(TARGET_ABI_DIR)/cpu_loop.o exit.o fd-trans.o > + $(TARGET_ABI_DIR)/cpu_loop.o exit.o fd-trans.o \ > + ethtool.o > > obj-$(TARGET_HAS_BFLT) += flatload.o > obj-$(TARGET_I386) += vm86.o > diff --git a/linux-user/ethtool.c b/linux-user/ethtool.c > new file mode 100644 > index 0000000000..fac97b9ba1 > --- /dev/null > +++ b/linux-user/ethtool.c > @@ -0,0 +1,840 @@ > +/* > + * 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> > +#include "ethtool.h" > +#include "qemu.h" > +#include "syscall_defs.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 0713ae1311..fd6ac963ec 100644 > --- a/linux-user/ioctls.h > +++ b/linux-user/ioctls.h > @@ -238,6 +238,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/qemu.h b/linux-user/qemu.h > index 5c964389c1..43f00681f8 100644 > --- a/linux-user/qemu.h > +++ b/linux-user/qemu.h > @@ -231,6 +231,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 bfc4219104..41fea53716 100644 > --- a/linux-user/syscall.c > +++ b/linux-user/syscall.c > @@ -127,6 +127,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 */ > @@ -676,7 +677,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); > @@ -4732,16 +4733,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" > @@ -4839,6 +4830,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 70df1a94fb..e25a8cbcc8 100644 > --- a/linux-user/syscall_defs.h > +++ b/linux-user/syscall_defs.h > @@ -866,6 +866,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 */ > @@ -2776,4 +2778,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 3f1f033464..559924c752 100644 > --- a/linux-user/syscall_types.h > +++ b/linux-user/syscall_types.h > @@ -1,3 +1,4 @@ > + > STRUCT_SPECIAL(termios) > > STRUCT(winsize, > @@ -464,3 +465,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.28.0.220.ged08abb693-goog > >
diff --git a/linux-user/Makefile.objs b/linux-user/Makefile.objs index 1940910a73..971d43173a 100644 --- a/linux-user/Makefile.objs +++ b/linux-user/Makefile.objs @@ -1,7 +1,8 @@ obj-y = main.o syscall.o strace.o mmap.o signal.o \ elfload.o linuxload.o uaccess.o uname.o \ safe-syscall.o $(TARGET_ABI_DIR)/signal.o \ - $(TARGET_ABI_DIR)/cpu_loop.o exit.o fd-trans.o + $(TARGET_ABI_DIR)/cpu_loop.o exit.o fd-trans.o \ + ethtool.o obj-$(TARGET_HAS_BFLT) += flatload.o obj-$(TARGET_I386) += vm86.o diff --git a/linux-user/ethtool.c b/linux-user/ethtool.c new file mode 100644 index 0000000000..fac97b9ba1 --- /dev/null +++ b/linux-user/ethtool.c @@ -0,0 +1,840 @@ +/* + * 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> +#include "ethtool.h" +#include "qemu.h" +#include "syscall_defs.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 0713ae1311..fd6ac963ec 100644 --- a/linux-user/ioctls.h +++ b/linux-user/ioctls.h @@ -238,6 +238,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/qemu.h b/linux-user/qemu.h index 5c964389c1..43f00681f8 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -231,6 +231,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 bfc4219104..41fea53716 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -127,6 +127,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 */ @@ -676,7 +677,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); @@ -4732,16 +4733,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" @@ -4839,6 +4830,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 70df1a94fb..e25a8cbcc8 100644 --- a/linux-user/syscall_defs.h +++ b/linux-user/syscall_defs.h @@ -866,6 +866,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 */ @@ -2776,4 +2778,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 3f1f033464..559924c752 100644 --- a/linux-user/syscall_types.h +++ b/linux-user/syscall_types.h @@ -1,3 +1,4 @@ + STRUCT_SPECIAL(termios) STRUCT(winsize, @@ -464,3 +465,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; +}
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> --- v1 -> v2: Fix style problems. linux-user/Makefile.objs | 3 +- linux-user/ethtool.c | 840 ++++++++++++++++++++++++++++++++++ linux-user/ethtool.h | 20 + linux-user/ethtool_entries.h | 107 +++++ linux-user/ioctls.h | 2 + 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, 1712 insertions(+), 12 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