| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/16 12:47 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"VIRTIO_NET",
"VIRTIO_PCI"
],
"FocusSymbols": [
"virtnet_set_rxnfc",
"virtnet_get_rxnfc",
"virtio_admin_cap_id_list_query",
"virtio_admin_cap_get",
"virtio_admin_cap_set",
"virtio_admin_obj_create",
"virtio_admin_obj_destroy",
"vp_modern_avq_work"
],
"KMSANReasoning": "The patch series introduces flow filter support (ethtool rxnfc) for virtio-net. It adds new structures and functions to handle virtio admin commands and flow filter rules. \n\nA careful review of the memory allocations and data structures introduced by this patch shows that all new memory allocations for keys, masks, classifiers, rules, and admin command data are performed using `kzalloc` or `kzalloc_obj`, ensuring they are fully zero-initialized. For example:\n- `key = kzalloc(key_size, GFP_KERNEL);`\n- `c = kzalloc(classifier_size + ..., GFP_KERNEL);`\n- `ff_rule = kzalloc(sizeof(*ff_rule) + key_size, GFP_KERNEL);`\n- `data = kzalloc_obj(*data);` in admin command functions.\n\nFurthermore, local structures like `struct virtio_admin_cmd cmd = {};` and `struct virtio_net_resource_obj_ff_group ethtool_group = {};` are explicitly zero-initialized.\n\nWhen copying data back to user space via ethtool (`virtnet_ethtool_get_flow`, `virtnet_ethtool_get_all_flows`), the code only copies back the `flow_spec` that was previously provided by the user and stored in the fully zeroed `eth_rule` structure.\n\nSince all memory introduced or modified by this patch is properly initialized before being used, sent to the device, or copied to user space, there is no risk of uninitialized memory reads or info-leaks. Standard KASAN is sufficient to catch any potential out-of-bounds accesses or use-after-free bugs in this code.",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces flow filter support (ethtool rxnfc) to the virtio-net driver, utilizing the new virtio admin command interface. This is a significant functional addition to a core virtualized network driver that is highly reachable in virtualized environments like QEMU/GCE. The changes include new ethtool callbacks and a new virtio admin command API, which should be fuzzed to ensure robustness.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/16 12:47 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 909d8d1fc9466a0a8af9348a0aec89b0e820e9e7\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sun Aug 16 12:47:41 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c\nindex aecfba7deeb03..7247f6cbdb32d 100644\n--- a/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c\n+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c\n@@ -53,11 +53,6 @@ struct mlx5e_ethtool_steering {\n \n static int flow_type_to_traffic_type(u32 flow_type);\n \n-static u32 flow_type_mask(u32 flow_type)\n-{\n-\treturn flow_type \u0026 ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS);\n-}\n-\n struct mlx5e_ethtool_rule {\n \tstruct list_head list;\n \tstruct ethtool_rx_flow_spec flow_spec;\n@@ -91,7 +86,7 @@ static struct mlx5e_ethtool_table *get_flow_table(struct mlx5e_priv *priv,\n \tint table_size;\n \tint prio;\n \n-\tswitch (flow_type_mask(fs-\u003eflow_type)) {\n+\tswitch (ethtool_flow_type_mask(fs-\u003eflow_type)) {\n \tcase TCP_V4_FLOW:\n \tcase UDP_V4_FLOW:\n \tcase TCP_V6_FLOW:\n@@ -350,7 +345,7 @@ static int set_flow_attrs(u32 *match_c, u32 *match_v,\n \t\t\t\t\t outer_headers);\n \tvoid *outer_headers_v = MLX5_ADDR_OF(fte_match_param, match_v,\n \t\t\t\t\t outer_headers);\n-\tu32 flow_type = flow_type_mask(fs-\u003eflow_type);\n+\tu32 flow_type = ethtool_flow_type_mask(fs-\u003eflow_type);\n \n \tswitch (flow_type) {\n \tcase TCP_V4_FLOW:\n@@ -435,7 +430,7 @@ static int flow_get_tirn(struct mlx5e_priv *priv,\n \t\tif (!rss)\n \t\t\treturn -ENOENT;\n \n-\t\tflow_type = flow_type_mask(fs-\u003eflow_type);\n+\t\tflow_type = ethtool_flow_type_mask(fs-\u003eflow_type);\n \t\ttt = flow_type_to_traffic_type(flow_type);\n \t\tif (tt \u003c 0)\n \t\t\treturn -EINVAL;\n@@ -673,7 +668,7 @@ static int validate_flow(struct mlx5e_priv *priv,\n \t\tif (fs-\u003ering_cookie \u003e= priv-\u003echannels.params.num_channels)\n \t\t\treturn -EINVAL;\n \n-\tswitch (flow_type_mask(fs-\u003eflow_type)) {\n+\tswitch (ethtool_flow_type_mask(fs-\u003eflow_type)) {\n \tcase ETHER_FLOW:\n \t\tnum_tuples += validate_ethter(fs);\n \t\tbreak;\n@@ -906,7 +901,7 @@ int mlx5e_ethtool_set_rxfh_fields(struct mlx5e_priv *priv,\n \n \trss_idx = nfc-\u003erss_context;\n \n-\tflow_type = flow_type_mask(nfc-\u003eflow_type);\n+\tflow_type = ethtool_flow_type_mask(nfc-\u003eflow_type);\n \ttt = flow_type_to_traffic_type(flow_type);\n \tif (tt \u003c 0)\n \t\treturn tt;\n@@ -951,7 +946,7 @@ int mlx5e_ethtool_get_rxfh_fields(struct mlx5e_priv *priv,\n \n \trss_idx = nfc-\u003erss_context;\n \n-\tflow_type = flow_type_mask(nfc-\u003eflow_type);\n+\tflow_type = ethtool_flow_type_mask(nfc-\u003eflow_type);\n \ttt = flow_type_to_traffic_type(flow_type);\n \tif (tt \u003c 0)\n \t\treturn tt;\ndiff --git a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c\nindex 01ddc3def9ac0..83eaceddc4375 100644\n--- a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c\n+++ b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c\n@@ -233,11 +233,6 @@ static int mlx5i_get_link_ksettings(struct net_device *netdev,\n \treturn 0;\n }\n \n-static u32 mlx5i_flow_type_mask(u32 flow_type)\n-{\n-\treturn flow_type \u0026 ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS);\n-}\n-\n static int mlx5i_set_rxfh_fields(struct net_device *dev,\n \t\t\t\t const struct ethtool_rxfh_fields *cmd,\n \t\t\t\t struct netlink_ext_ack *extack)\n@@ -260,7 +255,7 @@ static int mlx5i_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)\n \tstruct mlx5e_priv *priv = mlx5i_epriv(dev);\n \tstruct ethtool_rx_flow_spec *fs = \u0026cmd-\u003efs;\n \n-\tif (mlx5i_flow_type_mask(fs-\u003eflow_type) == ETHER_FLOW)\n+\tif (ethtool_flow_type_mask(fs-\u003eflow_type) == ETHER_FLOW)\n \t\treturn -EINVAL;\n \n \treturn mlx5e_ethtool_set_rxnfc(priv, cmd);\ndiff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c\nindex 3e2a5876c6c8c..87365ca05c259 100644\n--- a/drivers/net/virtio_net.c\n+++ b/drivers/net/virtio_net.c\n@@ -27,6 +27,15 @@\n #include \u003cnet/netdev_queues.h\u003e\n #include \u003cnet/xdp_sock_drv.h\u003e\n #include \u003cnet/page_pool/helpers.h\u003e\n+#include \u003clinux/virtio_admin.h\u003e\n+#include \u003cnet/ipv6.h\u003e\n+#include \u003cnet/ip.h\u003e\n+#include \u003cuapi/linux/virtio_net_ff.h\u003e\n+#include \u003clinux/tcp.h\u003e\n+#include \u003clinux/udp.h\u003e\n+#include \u003clinux/xarray.h\u003e\n+#include \u003clinux/refcount.h\u003e\n+#include \u003clinux/unaligned.h\u003e\n \n static int napi_weight = NAPI_POLL_WEIGHT;\n module_param(napi_weight, int, 0444);\n@@ -282,6 +291,24 @@ static const struct virtnet_stat_desc virtnet_stats_tx_speed_desc_qstat[] = {\n \tVIRTNET_STATS_DESC_TX_QSTAT(speed, ratelimit_packets, hw_drop_ratelimits),\n };\n \n+struct virtnet_ethtool_ff {\n+\tstruct xarray rules;\n+\tint num_rules;\n+};\n+\n+#define VIRTNET_FF_ETHTOOL_GROUP_PRIORITY 0\n+#define VIRTNET_FF_MAX_GROUPS 1\n+\n+struct virtnet_ff {\n+\tstruct virtio_device *vdev;\n+\tbool ff_supported;\n+\tstruct virtio_net_ff_cap_data *ff_caps;\n+\tstruct virtio_net_ff_cap_mask_data *ff_mask;\n+\tstruct virtio_net_ff_actions *ff_actions;\n+\tstruct xarray classifiers;\n+\tstruct virtnet_ethtool_ff ethtool;\n+};\n+\n #define VIRTNET_Q_TYPE_RX 0\n #define VIRTNET_Q_TYPE_TX 1\n #define VIRTNET_Q_TYPE_CQ 2\n@@ -474,6 +501,8 @@ struct virtnet_info {\n \n \tstruct virtio_net_rss_config_hdr *rss_hdr;\n \n+\tstruct virtnet_ff ff;\n+\n \t/* Must be last as it ends in a flexible-array member. */\n \tTRAILING_OVERLAP(struct virtio_net_rss_config_trailer, rss_trailer, hash_key_data,\n \t\tu8 rss_hash_key_data[NETDEV_RSS_KEY_LEN];\n@@ -519,6 +548,7 @@ static struct sk_buff *virtnet_skb_append_frag(struct receive_queue *rq,\n static void virtnet_xsk_completed(struct send_queue *sq, int num);\n static void free_unused_bufs(struct virtnet_info *vi);\n static void virtnet_del_vqs(struct virtnet_info *vi);\n+static void remove_vq_common(struct virtnet_info *vi);\n \n enum virtnet_xmit_type {\n \tVIRTNET_XMIT_TYPE_SKB,\n@@ -5587,34 +5617,6 @@ static u32 virtnet_get_rx_ring_count(struct net_device *dev)\n \treturn vi-\u003ecurr_queue_pairs;\n }\n \n-static const struct ethtool_ops virtnet_ethtool_ops = {\n-\t.supported_coalesce_params = ETHTOOL_COALESCE_MAX_FRAMES |\n-\t\tETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_USE_ADAPTIVE_RX,\n-\t.get_drvinfo = virtnet_get_drvinfo,\n-\t.get_link = ethtool_op_get_link,\n-\t.get_ringparam = virtnet_get_ringparam,\n-\t.set_ringparam = virtnet_set_ringparam,\n-\t.get_strings = virtnet_get_strings,\n-\t.get_sset_count = virtnet_get_sset_count,\n-\t.get_ethtool_stats = virtnet_get_ethtool_stats,\n-\t.set_channels = virtnet_set_channels,\n-\t.get_channels = virtnet_get_channels,\n-\t.get_ts_info = ethtool_op_get_ts_info,\n-\t.get_link_ksettings = virtnet_get_link_ksettings,\n-\t.set_link_ksettings = virtnet_set_link_ksettings,\n-\t.set_coalesce = virtnet_set_coalesce,\n-\t.get_coalesce = virtnet_get_coalesce,\n-\t.set_per_queue_coalesce = virtnet_set_per_queue_coalesce,\n-\t.get_per_queue_coalesce = virtnet_get_per_queue_coalesce,\n-\t.get_rxfh_key_size = virtnet_get_rxfh_key_size,\n-\t.get_rxfh_indir_size = virtnet_get_rxfh_indir_size,\n-\t.get_rxfh = virtnet_get_rxfh,\n-\t.set_rxfh = virtnet_set_rxfh,\n-\t.get_rxfh_fields = virtnet_get_hashflow,\n-\t.set_rxfh_fields = virtnet_set_hashflow,\n-\t.get_rx_ring_count = virtnet_get_rx_ring_count,\n-};\n-\n static void virtnet_get_queue_stats_rx(struct net_device *dev, int i,\n \t\t\t\t struct netdev_queue_stats_rx *stats)\n {\n@@ -5710,213 +5712,1423 @@ static const struct netdev_stat_ops virtnet_stat_ops = {\n \t.get_base_stats\t\t= virtnet_get_base_stats,\n };\n \n-static void virtnet_freeze_down(struct virtio_device *vdev)\n-{\n-\tstruct virtnet_info *vi = vdev-\u003epriv;\n+struct virtnet_ethtool_rule {\n+\tstruct ethtool_rx_flow_spec flow_spec;\n+\tu32 classifier_id;\n+};\n \n-\t/* Make sure no work handler is accessing the device */\n-\tflush_work(\u0026vi-\u003econfig_work);\n-\tdisable_rx_mode_work(vi);\n-\tflush_work(\u0026vi-\u003erx_mode_work);\n+/* The classifier struct must be the last field in this struct */\n+struct virtnet_classifier {\n+\tsize_t size;\n+\trefcount_t refcount;\n+\tu32 id;\n+\tstruct virtio_net_resource_obj_ff_classifier obj;\n+};\n \n-\tif (netif_running(vi-\u003edev)) {\n-\t\trtnl_lock();\n-\t\tvirtnet_close(vi-\u003edev);\n-\t\trtnl_unlock();\n+static_assert(sizeof(struct virtnet_classifier) ==\n+\t ALIGN(offsetofend(struct virtnet_classifier, obj),\n+\t\t __alignof__(struct virtnet_classifier)),\n+\t \"virtnet_classifier: classifier must be the last member\");\n+\n+static bool check_mask_vs_cap(const void *m, const void *c,\n+\t\t\t u16 len, bool partial)\n+{\n+\tconst u8 *mask = m;\n+\tconst u8 *cap = c;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c len; i++) {\n+\t\tif (partial \u0026\u0026 ((mask[i] \u0026 cap[i]) != mask[i]))\n+\t\t\treturn false;\n+\t\tif (!partial \u0026\u0026 mask[i] != cap[i])\n+\t\t\treturn false;\n \t}\n \n-\tnetif_tx_lock_bh(vi-\u003edev);\n-\tnetif_device_detach(vi-\u003edev);\n-\tnetif_tx_unlock_bh(vi-\u003edev);\n+\treturn true;\n }\n \n-static int init_vqs(struct virtnet_info *vi);\n+static\n+struct virtio_net_ff_selector *get_selector_cap(const struct virtnet_ff *ff,\n+\t\t\t\t\t\tu8 selector_type)\n+{\n+\tstruct virtio_net_ff_selector *sel;\n+\tvoid *buf;\n+\tint i;\n \n-static int virtnet_restore_up(struct virtio_device *vdev)\n+\tbuf = \u0026ff-\u003eff_mask-\u003eselectors;\n+\tsel = buf;\n+\n+\tfor (i = 0; i \u003c ff-\u003eff_mask-\u003ecount; i++) {\n+\t\tif (sel-\u003etype == selector_type)\n+\t\t\treturn sel;\n+\n+\t\tbuf += sizeof(struct virtio_net_ff_selector) + sel-\u003elength;\n+\t\tsel = buf;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static bool validate_eth_mask(const struct virtnet_ff *ff,\n+\t\t\t const struct virtio_net_ff_selector *sel,\n+\t\t\t const struct virtio_net_ff_selector *sel_cap)\n {\n-\tstruct virtnet_info *vi = vdev-\u003epriv;\n-\tint err;\n+\tbool partial_mask = !!(sel_cap-\u003eflags \u0026 VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);\n+\tstruct ethhdr *cap, *mask;\n+\tstruct ethhdr zeros = {};\n \n-\terr = init_vqs(vi);\n-\tif (err)\n-\t\treturn err;\n+\tcap = (struct ethhdr *)\u0026sel_cap-\u003emask;\n+\tmask = (struct ethhdr *)\u0026sel-\u003emask;\n \n-\terr = virtnet_create_page_pools(vi);\n-\tif (err)\n-\t\tgoto err_del_vqs;\n+\tif (memcmp(\u0026zeros.h_dest, mask-\u003eh_dest, sizeof(zeros.h_dest)) \u0026\u0026\n+\t !check_mask_vs_cap(mask-\u003eh_dest, cap-\u003eh_dest,\n+\t\t\t sizeof(mask-\u003eh_dest), partial_mask))\n+\t\treturn false;\n \n-\tvirtio_device_ready(vdev);\n+\tif (memcmp(\u0026zeros.h_source, mask-\u003eh_source, sizeof(zeros.h_source)) \u0026\u0026\n+\t !check_mask_vs_cap(mask-\u003eh_source, cap-\u003eh_source,\n+\t\t\t sizeof(mask-\u003eh_source), partial_mask))\n+\t\treturn false;\n \n-\tenable_rx_mode_work(vi);\n+\tif (mask-\u003eh_proto \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003eh_proto, \u0026cap-\u003eh_proto,\n+\t\t\t sizeof(__be16), partial_mask))\n+\t\treturn false;\n \n-\tif (netif_running(vi-\u003edev)) {\n-\t\trtnl_lock();\n-\t\terr = virtnet_open(vi-\u003edev);\n-\t\trtnl_unlock();\n-\t\tif (err)\n-\t\t\tgoto err_destroy_pools;\n-\t}\n+\treturn true;\n+}\n \n-\tnetif_tx_lock_bh(vi-\u003edev);\n-\tnetif_device_attach(vi-\u003edev);\n-\tnetif_tx_unlock_bh(vi-\u003edev);\n-\treturn 0;\n+static bool validate_ip4_mask(const struct virtnet_ff *ff,\n+\t\t\t const struct virtio_net_ff_selector *sel,\n+\t\t\t const struct virtio_net_ff_selector *sel_cap)\n+{\n+\tbool partial_mask = !!(sel_cap-\u003eflags \u0026 VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);\n+\tstruct iphdr *cap, *mask;\n \n-err_destroy_pools:\n-\tvirtio_reset_device(vdev);\n-\tfree_unused_bufs(vi);\n-\tvirtnet_destroy_page_pools(vi);\n-\tvirtnet_del_vqs(vi);\n-\treturn err;\n+\tcap = (struct iphdr *)\u0026sel_cap-\u003emask;\n+\tmask = (struct iphdr *)\u0026sel-\u003emask;\n \n-err_del_vqs:\n-\tvirtio_reset_device(vdev);\n-\tvirtnet_del_vqs(vi);\n-\treturn err;\n+\tif (get_unaligned(\u0026mask-\u003esaddr) \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003esaddr, \u0026cap-\u003esaddr,\n+\t\t\t sizeof(__be32), partial_mask))\n+\t\treturn false;\n+\n+\tif (get_unaligned(\u0026mask-\u003edaddr) \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003edaddr, \u0026cap-\u003edaddr,\n+\t\t\t sizeof(__be32), partial_mask))\n+\t\treturn false;\n+\n+\tif (mask-\u003eprotocol \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003eprotocol, \u0026cap-\u003eprotocol,\n+\t\t\t sizeof(u8), partial_mask))\n+\t\treturn false;\n+\n+\tif (mask-\u003etos \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003etos, \u0026cap-\u003etos,\n+\t\t\t sizeof(u8), partial_mask))\n+\t\treturn false;\n+\n+\treturn true;\n }\n \n-static int virtnet_set_guest_offloads(struct virtnet_info *vi, u64 offloads)\n+static bool validate_ip6_mask(const struct virtnet_ff *ff,\n+\t\t\t const struct virtio_net_ff_selector *sel,\n+\t\t\t const struct virtio_net_ff_selector *sel_cap)\n {\n-\t__virtio64 *_offloads __free(kfree) = NULL;\n-\tstruct scatterlist sg;\n+\tbool partial_mask = !!(sel_cap-\u003eflags \u0026 VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);\n+\tstruct in6_addr tmp;\n+\tstruct ipv6hdr *cap, *mask;\n \n-\t_offloads = kzalloc_obj(*_offloads);\n-\tif (!_offloads)\n-\t\treturn -ENOMEM;\n+\tcap = (struct ipv6hdr *)\u0026sel_cap-\u003emask;\n+\tmask = (struct ipv6hdr *)\u0026sel-\u003emask;\n \n-\t*_offloads = cpu_to_virtio64(vi-\u003evdev, offloads);\n+\t/* mask-\u003esaddr/daddr may be unaligned; copy to aligned tmp for\n+\t * ipv6_addr_any().\n+\t */\n+\tmemcpy(\u0026tmp, \u0026mask-\u003esaddr, sizeof(tmp));\n+\tif (!ipv6_addr_any(\u0026tmp) \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003esaddr, \u0026cap-\u003esaddr,\n+\t\t\t sizeof(cap-\u003esaddr), partial_mask))\n+\t\treturn false;\n \n-\tsg_init_one(\u0026sg, _offloads, sizeof(*_offloads));\n+\tmemcpy(\u0026tmp, \u0026mask-\u003edaddr, sizeof(tmp));\n+\tif (!ipv6_addr_any(\u0026tmp) \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003edaddr, \u0026cap-\u003edaddr,\n+\t\t\t sizeof(cap-\u003edaddr), partial_mask))\n+\t\treturn false;\n \n-\tif (!virtnet_send_command(vi, VIRTIO_NET_CTRL_GUEST_OFFLOADS,\n-\t\t\t\t VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, \u0026sg)) {\n-\t\tdev_warn(\u0026vi-\u003edev-\u003edev, \"Fail to set guest offload.\\n\");\n-\t\treturn -EINVAL;\n-\t}\n+\tif (mask-\u003enexthdr \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003enexthdr, \u0026cap-\u003enexthdr,\n+\t\t\t sizeof(cap-\u003enexthdr), partial_mask))\n+\t\treturn false;\n \n-\treturn 0;\n+\treturn true;\n }\n \n-static int virtnet_clear_guest_offloads(struct virtnet_info *vi)\n+static bool validate_tcp_mask(const struct virtnet_ff *ff,\n+\t\t\t const struct virtio_net_ff_selector *sel,\n+\t\t\t const struct virtio_net_ff_selector *sel_cap)\n {\n-\tu64 offloads = 0;\n+\tbool partial_mask = !!(sel_cap-\u003eflags \u0026 VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);\n+\tstruct tcphdr *cap, *mask;\n \n-\tif (!vi-\u003eguest_offloads)\n-\t\treturn 0;\n+\tcap = (struct tcphdr *)\u0026sel_cap-\u003emask;\n+\tmask = (struct tcphdr *)\u0026sel-\u003emask;\n \n-\treturn virtnet_set_guest_offloads(vi, offloads);\n+\tif (get_unaligned(\u0026mask-\u003esource) \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003esource, \u0026cap-\u003esource,\n+\t\t\t sizeof(cap-\u003esource), partial_mask))\n+\t\treturn false;\n+\n+\tif (get_unaligned(\u0026mask-\u003edest) \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003edest, \u0026cap-\u003edest,\n+\t\t\t sizeof(cap-\u003edest), partial_mask))\n+\t\treturn false;\n+\n+\treturn true;\n }\n \n-static int virtnet_restore_guest_offloads(struct virtnet_info *vi)\n+static bool validate_udp_mask(const struct virtnet_ff *ff,\n+\t\t\t const struct virtio_net_ff_selector *sel,\n+\t\t\t const struct virtio_net_ff_selector *sel_cap)\n {\n-\tu64 offloads = vi-\u003eguest_offloads;\n+\tbool partial_mask = !!(sel_cap-\u003eflags \u0026 VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);\n+\tstruct udphdr *cap, *mask;\n \n-\tif (!vi-\u003eguest_offloads)\n-\t\treturn 0;\n+\tcap = (struct udphdr *)\u0026sel_cap-\u003emask;\n+\tmask = (struct udphdr *)\u0026sel-\u003emask;\n \n-\treturn virtnet_set_guest_offloads(vi, offloads);\n+\tif (get_unaligned(\u0026mask-\u003esource) \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003esource, \u0026cap-\u003esource,\n+\t\t\t sizeof(cap-\u003esource), partial_mask))\n+\t\treturn false;\n+\n+\tif (get_unaligned(\u0026mask-\u003edest) \u0026\u0026\n+\t !check_mask_vs_cap(\u0026mask-\u003edest, \u0026cap-\u003edest,\n+\t\t\t sizeof(cap-\u003edest), partial_mask))\n+\t\treturn false;\n+\n+\treturn true;\n }\n \n-static int virtnet_rq_bind_xsk_pool(struct virtnet_info *vi, struct receive_queue *rq,\n-\t\t\t\t struct xsk_buff_pool *pool)\n+static bool validate_mask(const struct virtnet_ff *ff,\n+\t\t\t const struct virtio_net_ff_selector *sel)\n {\n-\tint err, qindex;\n+\tstruct virtio_net_ff_selector *sel_cap = get_selector_cap(ff, sel-\u003etype);\n \n-\tqindex = rq - vi-\u003erq;\n+\tif (!sel_cap)\n+\t\treturn false;\n \n-\tif (pool) {\n-\t\terr = xdp_rxq_info_reg(\u0026rq-\u003exsk_rxq_info, vi-\u003edev, qindex, rq-\u003enapi.napi_id);\n-\t\tif (err \u003c 0)\n-\t\t\treturn err;\n+\tswitch (sel-\u003etype) {\n+\tcase VIRTIO_NET_FF_MASK_TYPE_ETH:\n+\t\treturn validate_eth_mask(ff, sel, sel_cap);\n \n-\t\terr = xdp_rxq_info_reg_mem_model(\u0026rq-\u003exsk_rxq_info,\n-\t\t\t\t\t\t MEM_TYPE_XSK_BUFF_POOL, NULL);\n-\t\tif (err \u003c 0)\n-\t\t\tgoto unreg;\n+\tcase VIRTIO_NET_FF_MASK_TYPE_IPV4:\n+\t\treturn validate_ip4_mask(ff, sel, sel_cap);\n \n-\t\txsk_pool_set_rxq_info(pool, \u0026rq-\u003exsk_rxq_info);\n+\tcase VIRTIO_NET_FF_MASK_TYPE_IPV6:\n+\t\treturn validate_ip6_mask(ff, sel, sel_cap);\n+\n+\tcase VIRTIO_NET_FF_MASK_TYPE_TCP:\n+\t\treturn validate_tcp_mask(ff, sel, sel_cap);\n+\n+\tcase VIRTIO_NET_FF_MASK_TYPE_UDP:\n+\t\treturn validate_udp_mask(ff, sel, sel_cap);\n \t}\n \n-\tvirtnet_rx_pause(vi, rq);\n+\treturn false;\n+}\n \n-\terr = virtqueue_reset(rq-\u003evq, virtnet_rq_unmap_free_buf, NULL);\n-\tif (err) {\n-\t\tnetdev_err(vi-\u003edev, \"reset rx fail: rx queue index: %d err: %d\\n\", qindex, err);\n+static void set_tcp(struct tcphdr *mask, struct tcphdr *key,\n+\t\t __be16 psrc_m, __be16 psrc_k,\n+\t\t __be16 pdst_m, __be16 pdst_k)\n+{\n+\t/* mask/key may be unaligned; use memcpy */\n+\tif (psrc_m) {\n+\t\tmemcpy(\u0026mask-\u003esource, \u0026psrc_m, sizeof(mask-\u003esource));\n+\t\tmemcpy(\u0026key-\u003esource, \u0026psrc_k, sizeof(key-\u003esource));\n+\t}\n+\tif (pdst_m) {\n+\t\tmemcpy(\u0026mask-\u003edest, \u0026pdst_m, sizeof(mask-\u003edest));\n+\t\tmemcpy(\u0026key-\u003edest, \u0026pdst_k, sizeof(key-\u003edest));\n+\t}\n+}\n \n-\t\tpool = NULL;\n+static void set_udp(struct udphdr *mask, struct udphdr *key,\n+\t\t __be16 psrc_m, __be16 psrc_k,\n+\t\t __be16 pdst_m, __be16 pdst_k)\n+{\n+\t/* mask/key may be unaligned; use memcpy */\n+\tif (psrc_m) {\n+\t\tmemcpy(\u0026mask-\u003esource, \u0026psrc_m, sizeof(mask-\u003esource));\n+\t\tmemcpy(\u0026key-\u003esource, \u0026psrc_k, sizeof(key-\u003esource));\n+\t}\n+\tif (pdst_m) {\n+\t\tmemcpy(\u0026mask-\u003edest, \u0026pdst_m, sizeof(mask-\u003edest));\n+\t\tmemcpy(\u0026key-\u003edest, \u0026pdst_k, sizeof(key-\u003edest));\n \t}\n+}\n \n-\trq-\u003exsk_pool = pool;\n+static void parse_ip4(struct iphdr *mask, struct iphdr *key,\n+\t\t const struct ethtool_rx_flow_spec *fs)\n+{\n+\tconst struct ethtool_usrip4_spec *l3_mask = \u0026fs-\u003em_u.usr_ip4_spec;\n+\tconst struct ethtool_usrip4_spec *l3_val = \u0026fs-\u003eh_u.usr_ip4_spec;\n \n-\tvirtnet_rx_resume(vi, rq, true);\n+\tif (l3_mask-\u003eip4src) {\n+\t\tput_unaligned(l3_mask-\u003eip4src, \u0026mask-\u003esaddr);\n+\t\tput_unaligned(l3_val-\u003eip4src, \u0026key-\u003esaddr);\n+\t}\n \n-\tif (pool)\n-\t\treturn 0;\n+\tif (l3_mask-\u003eip4dst) {\n+\t\tput_unaligned(l3_mask-\u003eip4dst, \u0026mask-\u003edaddr);\n+\t\tput_unaligned(l3_val-\u003eip4dst, \u0026key-\u003edaddr);\n+\t}\n \n-unreg:\n-\txdp_rxq_info_unreg(\u0026rq-\u003exsk_rxq_info);\n-\treturn err;\n+\tif (l3_mask-\u003etos) {\n+\t\tmask-\u003etos = l3_mask-\u003etos;\n+\t\tkey-\u003etos = l3_val-\u003etos;\n+\t}\n }\n \n-static int virtnet_sq_bind_xsk_pool(struct virtnet_info *vi,\n-\t\t\t\t struct send_queue *sq,\n-\t\t\t\t struct xsk_buff_pool *pool)\n+static void parse_ip6(struct ipv6hdr *mask, struct ipv6hdr *key,\n+\t\t const struct ethtool_rx_flow_spec *fs)\n {\n-\tint err, qindex;\n-\n-\tqindex = sq - vi-\u003esq;\n+\tconst struct ethtool_usrip6_spec *l3_mask = \u0026fs-\u003em_u.usr_ip6_spec;\n+\tconst struct ethtool_usrip6_spec *l3_val = \u0026fs-\u003eh_u.usr_ip6_spec;\n \n-\tvirtnet_tx_pause(vi, sq);\n+\tif (!ipv6_addr_any((struct in6_addr *)l3_mask-\u003eip6src)) {\n+\t\tmemcpy(\u0026mask-\u003esaddr, l3_mask-\u003eip6src, sizeof(mask-\u003esaddr));\n+\t\tmemcpy(\u0026key-\u003esaddr, l3_val-\u003eip6src, sizeof(key-\u003esaddr));\n+\t}\n \n-\terr = virtqueue_reset(sq-\u003evq, virtnet_sq_free_unused_buf,\n-\t\t\t virtnet_sq_free_unused_buf_done);\n-\tif (err) {\n-\t\tnetdev_err(vi-\u003edev, \"reset tx fail: tx queue index: %d err: %d\\n\", qindex, err);\n-\t\tpool = NULL;\n+\tif (!ipv6_addr_any((struct in6_addr *)l3_mask-\u003eip6dst)) {\n+\t\tmemcpy(\u0026mask-\u003edaddr, l3_mask-\u003eip6dst, sizeof(mask-\u003edaddr));\n+\t\tmemcpy(\u0026key-\u003edaddr, l3_val-\u003eip6dst, sizeof(key-\u003edaddr));\n \t}\n+}\n \n-\tsq-\u003exsk_pool = pool;\n+static bool has_ipv4(u32 flow_type)\n+{\n+\treturn flow_type == TCP_V4_FLOW ||\n+\t flow_type == UDP_V4_FLOW ||\n+\t flow_type == IP_USER_FLOW;\n+}\n \n-\tvirtnet_tx_resume(vi, sq);\n+static bool has_ipv6(u32 flow_type)\n+{\n+\treturn flow_type == TCP_V6_FLOW ||\n+\t flow_type == UDP_V6_FLOW ||\n+\t flow_type == IPV6_USER_FLOW;\n+}\n \n-\treturn err;\n+static bool has_tcp(u32 flow_type)\n+{\n+\treturn flow_type == TCP_V4_FLOW || flow_type == TCP_V6_FLOW;\n }\n \n-static int virtnet_xsk_pool_enable(struct net_device *dev,\n-\t\t\t\t struct xsk_buff_pool *pool,\n-\t\t\t\t u16 qid)\n+static bool has_udp(u32 flow_type)\n {\n-\tstruct virtnet_info *vi = netdev_priv(dev);\n-\tstruct receive_queue *rq;\n-\tstruct device *dma_dev;\n-\tstruct send_queue *sq;\n-\tdma_addr_t hdr_dma;\n-\tint err, size;\n+\treturn flow_type == UDP_V4_FLOW || flow_type == UDP_V6_FLOW;\n+}\n \n-\tif (vi-\u003ehdr_len \u003e xsk_pool_get_headroom(pool))\n-\t\treturn -EINVAL;\n+static int setup_classifier(struct virtnet_ff *ff,\n+\t\t\t struct virtnet_classifier **c)\n+{\n+\tstruct virtnet_classifier *tmp;\n+\tunsigned long i;\n+\tint err;\n \n-\t/* In big_packets mode, xdp cannot work, so there is no need to\n-\t * initialize xsk of rq.\n-\t */\n-\tif (!vi-\u003erq[qid].page_pool)\n-\t\treturn -ENOENT;\n+\txa_for_each(\u0026ff-\u003eclassifiers, i, tmp) {\n+\t\tif ((*c)-\u003esize == tmp-\u003esize \u0026\u0026\n+\t\t !memcmp(\u0026tmp-\u003eobj, \u0026(*c)-\u003eobj, tmp-\u003esize)) {\n+\t\t\trefcount_inc(\u0026tmp-\u003erefcount);\n+\t\t\tkfree(*c);\n+\t\t\t*c = tmp;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n \n-\tif (qid \u003e= vi-\u003ecurr_queue_pairs)\n-\t\treturn -EINVAL;\n+\terr = xa_alloc(\u0026ff-\u003eclassifiers, \u0026(*c)-\u003eid, *c,\n+\t\t XA_LIMIT(0, le32_to_cpu(ff-\u003eff_caps-\u003eclassifiers_limit) - 1),\n+\t\t GFP_KERNEL);\n+\tif (err)\n+\t\treturn err;\n \n-\tsq = \u0026vi-\u003esq[qid];\n-\trq = \u0026vi-\u003erq[qid];\n+\terr = virtio_admin_obj_create(ff-\u003evdev,\n+\t\t\t\t VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER,\n+\t\t\t\t (*c)-\u003eid,\n+\t\t\t\t VIRTIO_ADMIN_GROUP_TYPE_SELF,\n+\t\t\t\t 0,\n+\t\t\t\t \u0026(*c)-\u003eobj,\n+\t\t\t\t (*c)-\u003esize);\n+\tif (err)\n+\t\tgoto err_xarray;\n \n-\t/* xsk assumes that tx and rx must have the same dma device. The af-xdp\n-\t * may use one buffer to receive from the rx and reuse this buffer to\n-\t * send by the tx. So the dma dev of sq and rq must be the same one.\n-\t *\n-\t * But vq-\u003edma_dev allows every vq has the respective dma dev. So I\n-\t * check the dma dev of vq and sq is the same dev.\n-\t */\n-\tif (virtqueue_dma_dev(rq-\u003evq) != virtqueue_dma_dev(sq-\u003evq))\n-\t\treturn -EINVAL;\n+\trefcount_set(\u0026(*c)-\u003erefcount, 1);\n+out:\n+\treturn 0;\n+\n+err_xarray:\n+\txa_erase(\u0026ff-\u003eclassifiers, (*c)-\u003eid);\n+\n+\treturn err;\n+}\n+\n+static void try_destroy_classifier(struct virtnet_ff *ff, u32 classifier_id)\n+{\n+\tstruct virtnet_classifier *c;\n+\n+\tc = xa_load(\u0026ff-\u003eclassifiers, classifier_id);\n+\tif (c \u0026\u0026 refcount_dec_and_test(\u0026c-\u003erefcount)) {\n+\t\tvirtio_admin_obj_destroy(ff-\u003evdev,\n+\t\t\t\t\t VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER,\n+\t\t\t\t\t c-\u003eid,\n+\t\t\t\t\t VIRTIO_ADMIN_GROUP_TYPE_SELF,\n+\t\t\t\t\t 0);\n+\n+\t\txa_erase(\u0026ff-\u003eclassifiers, c-\u003eid);\n+\t\tkfree(c);\n+\t}\n+}\n+\n+static void destroy_ethtool_rule(struct virtnet_ff *ff,\n+\t\t\t\t struct virtnet_ethtool_rule *eth_rule)\n+{\n+\tff-\u003eethtool.num_rules--;\n+\n+\tvirtio_admin_obj_destroy(ff-\u003evdev,\n+\t\t\t\t VIRTIO_NET_RESOURCE_OBJ_FF_RULE,\n+\t\t\t\t eth_rule-\u003eflow_spec.location,\n+\t\t\t\t VIRTIO_ADMIN_GROUP_TYPE_SELF,\n+\t\t\t\t 0);\n+\n+\txa_erase(\u0026ff-\u003eethtool.rules, eth_rule-\u003eflow_spec.location);\n+\ttry_destroy_classifier(ff, eth_rule-\u003eclassifier_id);\n+\tkfree(eth_rule);\n+}\n+\n+static int insert_rule(struct virtnet_ff *ff,\n+\t\t struct virtnet_ethtool_rule *eth_rule,\n+\t\t u32 classifier_id,\n+\t\t const u8 *key,\n+\t\t u8 key_size)\n+{\n+\tstruct ethtool_rx_flow_spec *fs = \u0026eth_rule-\u003eflow_spec;\n+\tstruct virtio_net_resource_obj_ff_rule *ff_rule;\n+\tint err;\n+\n+\tff_rule = kzalloc(sizeof(*ff_rule) + key_size, GFP_KERNEL);\n+\tif (!ff_rule)\n+\t\treturn -ENOMEM;\n+\n+\t/* Intentionally leave the priority as 0. All rules have the same\n+\t * priority.\n+\t */\n+\tff_rule-\u003egroup_id = cpu_to_le32(VIRTNET_FF_ETHTOOL_GROUP_PRIORITY);\n+\tff_rule-\u003eclassifier_id = cpu_to_le32(classifier_id);\n+\tff_rule-\u003ekey_length = key_size;\n+\tff_rule-\u003eaction = fs-\u003ering_cookie == RX_CLS_FLOW_DISC ?\n+\t\t\t\t\t VIRTIO_NET_FF_ACTION_DROP :\n+\t\t\t\t\t VIRTIO_NET_FF_ACTION_RX_VQ;\n+\tff_rule-\u003evq_index = fs-\u003ering_cookie != RX_CLS_FLOW_DISC ?\n+\t\t\t\t\t cpu_to_le16(rxq2vq(fs-\u003ering_cookie)) : 0;\n+\tmemcpy(\u0026ff_rule-\u003ekeys, key, key_size);\n+\n+\terr = virtio_admin_obj_create(ff-\u003evdev,\n+\t\t\t\t VIRTIO_NET_RESOURCE_OBJ_FF_RULE,\n+\t\t\t\t fs-\u003elocation,\n+\t\t\t\t VIRTIO_ADMIN_GROUP_TYPE_SELF,\n+\t\t\t\t 0,\n+\t\t\t\t ff_rule,\n+\t\t\t\t sizeof(*ff_rule) + key_size);\n+\tif (err)\n+\t\tgoto err_ff_rule;\n+\n+\teth_rule-\u003eclassifier_id = classifier_id;\n+\tff-\u003eethtool.num_rules++;\n+\tkfree(ff_rule);\n+\tkfree(key);\n+\n+\treturn 0;\n+\n+err_ff_rule:\n+\tkfree(ff_rule);\n+\n+\treturn err;\n+}\n+\n+static bool supported_flow_type(const struct ethtool_rx_flow_spec *fs)\n+{\n+\tswitch (fs-\u003eflow_type) {\n+\tcase ETHER_FLOW:\n+\tcase IP_USER_FLOW:\n+\tcase IPV6_USER_FLOW:\n+\tcase TCP_V4_FLOW:\n+\tcase TCP_V6_FLOW:\n+\tcase UDP_V4_FLOW:\n+\tcase UDP_V6_FLOW:\n+\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n+static int validate_flow_input(struct virtnet_ff *ff,\n+\t\t\t const struct ethtool_rx_flow_spec *fs,\n+\t\t\t u16 curr_queue_pairs)\n+{\n+\tu8 required_action = fs-\u003ering_cookie == RX_CLS_FLOW_DISC ?\n+\t\t\t VIRTIO_NET_FF_ACTION_DROP :\n+\t\t\t VIRTIO_NET_FF_ACTION_RX_VQ;\n+\tint i;\n+\n+\t/* Force users to use RX_CLS_LOC_ANY - don't allow specific locations */\n+\tif (fs-\u003elocation != RX_CLS_LOC_ANY)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (fs-\u003ering_cookie != RX_CLS_FLOW_DISC \u0026\u0026\n+\t fs-\u003ering_cookie \u003e= curr_queue_pairs)\n+\t\treturn -EINVAL;\n+\n+\tfor (i = 0; i \u003c ff-\u003eff_actions-\u003ecount; i++)\n+\t\tif (ff-\u003eff_actions-\u003eactions[i] == required_action)\n+\t\t\tgoto action_ok;\n+\treturn -EOPNOTSUPP;\n+\n+action_ok:\n+\tif (fs-\u003eflow_type != ethtool_flow_type_mask(fs-\u003eflow_type))\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (!supported_flow_type(fs))\n+\t\treturn -EOPNOTSUPP;\n+\n+\treturn 0;\n+}\n+\n+static void calculate_flow_sizes(struct ethtool_rx_flow_spec *fs,\n+\t\t\t\t u8 *key_size, size_t *classifier_size,\n+\t\t\t\t int *num_hdrs)\n+{\n+\tsize_t size = sizeof(struct ethhdr);\n+\n+\t*num_hdrs = 1;\n+\n+\tif (fs-\u003eflow_type != ETHER_FLOW) {\n+\t\t++(*num_hdrs);\n+\t\tif (has_ipv4(fs-\u003eflow_type))\n+\t\t\tsize += sizeof(struct iphdr);\n+\t\telse if (has_ipv6(fs-\u003eflow_type))\n+\t\t\tsize += sizeof(struct ipv6hdr);\n+\n+\t\tif (has_tcp(fs-\u003eflow_type) || has_udp(fs-\u003eflow_type)) {\n+\t\t\t++(*num_hdrs);\n+\t\t\tsize += has_tcp(fs-\u003eflow_type) ? sizeof(struct tcphdr) :\n+\t\t\t\t\t\t\t sizeof(struct udphdr);\n+\t\t}\n+\t}\n+\n+\tBUG_ON(size \u003e 0xff);\n+\t*key_size = size;\n+\t/*\n+\t * The classifier size is the size of the classifier header, a selector\n+\t * header for each type of header in the match criteria, and each header\n+\t * providing the mask for matching against.\n+\t */\n+\t*classifier_size = *key_size +\n+\t\t\t sizeof(struct virtio_net_resource_obj_ff_classifier) +\n+\t\t\t sizeof(struct virtio_net_ff_selector) * (*num_hdrs);\n+}\n+\n+static void setup_eth_hdr_key_mask(struct virtio_net_ff_selector *selector,\n+\t\t\t\t u8 *key,\n+\t\t\t\t const struct ethtool_rx_flow_spec *fs,\n+\t\t\t\t int num_hdrs)\n+{\n+\tstruct ethhdr *eth_m = (struct ethhdr *)\u0026selector-\u003emask;\n+\tstruct ethhdr *eth_k = (struct ethhdr *)key;\n+\n+\tselector-\u003etype = VIRTIO_NET_FF_MASK_TYPE_ETH;\n+\tselector-\u003elength = sizeof(struct ethhdr);\n+\n+\tif (num_hdrs \u003e 1) {\n+\t\teth_m-\u003eh_proto = cpu_to_be16(0xffff);\n+\t\tif (has_ipv4(fs-\u003eflow_type))\n+\t\t\teth_k-\u003eh_proto = cpu_to_be16(ETH_P_IP);\n+\t\telse\n+\t\t\teth_k-\u003eh_proto = cpu_to_be16(ETH_P_IPV6);\n+\t} else {\n+\t\tmemcpy(eth_m, \u0026fs-\u003em_u.ether_spec, sizeof(*eth_m));\n+\t\tmemcpy(eth_k, \u0026fs-\u003eh_u.ether_spec, sizeof(*eth_k));\n+\t}\n+}\n+\n+static int setup_ip_key_mask(struct virtio_net_ff_selector *selector,\n+\t\t\t u8 *key,\n+\t\t\t const struct ethtool_rx_flow_spec *fs,\n+\t\t\t int num_hdrs)\n+{\n+\tstruct ipv6hdr *v6_m = (struct ipv6hdr *)\u0026selector-\u003emask;\n+\tstruct iphdr *v4_m = (struct iphdr *)\u0026selector-\u003emask;\n+\tstruct ipv6hdr *v6_k = (struct ipv6hdr *)key;\n+\tstruct iphdr *v4_k = (struct iphdr *)key;\n+\n+\tif (has_ipv6(fs-\u003eflow_type)) {\n+\t\tselector-\u003etype = VIRTIO_NET_FF_MASK_TYPE_IPV6;\n+\t\tselector-\u003elength = sizeof(struct ipv6hdr);\n+\n+\t\t/* exclude tclass, it's not exposed directly in struct ipv6hdr */\n+\t\tif (fs-\u003eh_u.usr_ip6_spec.tclass ||\n+\t\t fs-\u003em_u.usr_ip6_spec.tclass ||\n+\t\t (num_hdrs == 2 \u0026\u0026 (fs-\u003eh_u.usr_ip6_spec.l4_4_bytes ||\n+\t\t\t\t fs-\u003em_u.usr_ip6_spec.l4_4_bytes ||\n+\t\t\t\t fs-\u003eh_u.usr_ip6_spec.l4_proto ||\n+\t\t\t\t fs-\u003em_u.usr_ip6_spec.l4_proto)))\n+\t\t\treturn -EINVAL;\n+\n+\t\tparse_ip6(v6_m, v6_k, fs);\n+\n+\t\tif (num_hdrs \u003e 2) {\n+\t\t\tv6_m-\u003enexthdr = 0xff;\n+\t\t\tif (has_tcp(fs-\u003eflow_type))\n+\t\t\t\tv6_k-\u003enexthdr = IPPROTO_TCP;\n+\t\t\telse\n+\t\t\t\tv6_k-\u003enexthdr = IPPROTO_UDP;\n+\t\t}\n+\t} else {\n+\t\tselector-\u003etype = VIRTIO_NET_FF_MASK_TYPE_IPV4;\n+\t\tselector-\u003elength = sizeof(struct iphdr);\n+\n+\t\tif (num_hdrs == 2 \u0026\u0026\n+\t\t (fs-\u003eh_u.usr_ip4_spec.l4_4_bytes ||\n+\t\t fs-\u003eh_u.usr_ip4_spec.ip_ver != ETH_RX_NFC_IP4 ||\n+\t\t fs-\u003em_u.usr_ip4_spec.l4_4_bytes ||\n+\t\t fs-\u003em_u.usr_ip4_spec.ip_ver ||\n+\t\t fs-\u003em_u.usr_ip4_spec.proto))\n+\t\t\treturn -EINVAL;\n+\n+\t\tparse_ip4(v4_m, v4_k, fs);\n+\n+\t\tif (num_hdrs \u003e 2) {\n+\t\t\tv4_m-\u003eprotocol = 0xff;\n+\t\t\tif (has_tcp(fs-\u003eflow_type))\n+\t\t\t\tv4_k-\u003eprotocol = IPPROTO_TCP;\n+\t\t\telse\n+\t\t\t\tv4_k-\u003eprotocol = IPPROTO_UDP;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int setup_transport_key_mask(struct virtio_net_ff_selector *selector,\n+\t\t\t\t u8 *key,\n+\t\t\t\t struct ethtool_rx_flow_spec *fs)\n+{\n+\tstruct tcphdr *tcp_m = (struct tcphdr *)\u0026selector-\u003emask;\n+\tstruct udphdr *udp_m = (struct udphdr *)\u0026selector-\u003emask;\n+\tconst struct ethtool_tcpip6_spec *v6_l4_mask;\n+\tconst struct ethtool_tcpip4_spec *v4_l4_mask;\n+\tconst struct ethtool_tcpip6_spec *v6_l4_key;\n+\tconst struct ethtool_tcpip4_spec *v4_l4_key;\n+\tstruct tcphdr *tcp_k = (struct tcphdr *)key;\n+\tstruct udphdr *udp_k = (struct udphdr *)key;\n+\n+\tif (has_tcp(fs-\u003eflow_type)) {\n+\t\tselector-\u003etype = VIRTIO_NET_FF_MASK_TYPE_TCP;\n+\t\tselector-\u003elength = sizeof(struct tcphdr);\n+\n+\t\tif (has_ipv6(fs-\u003eflow_type)) {\n+\t\t\tv6_l4_mask = \u0026fs-\u003em_u.tcp_ip6_spec;\n+\t\t\tv6_l4_key = \u0026fs-\u003eh_u.tcp_ip6_spec;\n+\n+\t\t\tset_tcp(tcp_m, tcp_k, v6_l4_mask-\u003epsrc, v6_l4_key-\u003epsrc,\n+\t\t\t\tv6_l4_mask-\u003epdst, v6_l4_key-\u003epdst);\n+\t\t} else {\n+\t\t\tv4_l4_mask = \u0026fs-\u003em_u.tcp_ip4_spec;\n+\t\t\tv4_l4_key = \u0026fs-\u003eh_u.tcp_ip4_spec;\n+\n+\t\t\tset_tcp(tcp_m, tcp_k, v4_l4_mask-\u003epsrc, v4_l4_key-\u003epsrc,\n+\t\t\t\tv4_l4_mask-\u003epdst, v4_l4_key-\u003epdst);\n+\t\t}\n+\n+\t} else if (has_udp(fs-\u003eflow_type)) {\n+\t\tselector-\u003etype = VIRTIO_NET_FF_MASK_TYPE_UDP;\n+\t\tselector-\u003elength = sizeof(struct udphdr);\n+\n+\t\tif (has_ipv6(fs-\u003eflow_type)) {\n+\t\t\tv6_l4_mask = \u0026fs-\u003em_u.udp_ip6_spec;\n+\t\t\tv6_l4_key = \u0026fs-\u003eh_u.udp_ip6_spec;\n+\n+\t\t\tset_udp(udp_m, udp_k, v6_l4_mask-\u003epsrc, v6_l4_key-\u003epsrc,\n+\t\t\t\tv6_l4_mask-\u003epdst, v6_l4_key-\u003epdst);\n+\t\t} else {\n+\t\t\tv4_l4_mask = \u0026fs-\u003em_u.udp_ip4_spec;\n+\t\t\tv4_l4_key = \u0026fs-\u003eh_u.udp_ip4_spec;\n+\n+\t\t\tset_udp(udp_m, udp_k, v4_l4_mask-\u003epsrc, v4_l4_key-\u003epsrc,\n+\t\t\t\tv4_l4_mask-\u003epdst, v4_l4_key-\u003epdst);\n+\t\t}\n+\t} else {\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+validate_classifier_selectors(struct virtnet_ff *ff,\n+\t\t\t struct virtio_net_resource_obj_ff_classifier *classifier,\n+\t\t\t int num_hdrs)\n+{\n+\tstruct virtio_net_ff_selector *selector = (void *)classifier-\u003eselectors;\n+\tint i;\n+\n+\tif (num_hdrs \u003e ff-\u003eff_caps-\u003eselectors_per_classifier_limit)\n+\t\treturn -EINVAL;\n+\n+\tfor (i = 0; i \u003c num_hdrs; i++) {\n+\t\tif (!validate_mask(ff, selector))\n+\t\t\treturn -EINVAL;\n+\n+\t\tselector = (((void *)selector) + sizeof(*selector) +\n+\t\t\t\t\tselector-\u003elength);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static\n+struct virtio_net_ff_selector *next_selector(struct virtio_net_ff_selector *sel)\n+{\n+\treturn (void *)sel + sizeof(struct virtio_net_ff_selector) +\n+\t\tsel-\u003elength;\n+}\n+\n+static int build_and_insert(struct virtnet_ff *ff,\n+\t\t\t struct virtnet_ethtool_rule *eth_rule)\n+{\n+\tstruct virtio_net_resource_obj_ff_classifier *classifier;\n+\tstruct ethtool_rx_flow_spec *fs = \u0026eth_rule-\u003eflow_spec;\n+\tstruct virtio_net_ff_selector *selector;\n+\tstruct virtnet_classifier *c;\n+\tsize_t classifier_size;\n+\tsize_t key_offset;\n+\tint num_hdrs;\n+\tu8 key_size;\n+\tu8 *key;\n+\tint err;\n+\n+\tcalculate_flow_sizes(fs, \u0026key_size, \u0026classifier_size, \u0026num_hdrs);\n+\n+\tkey = kzalloc(key_size, GFP_KERNEL);\n+\tif (!key)\n+\t\treturn -ENOMEM;\n+\n+\t/*\n+\t * virtio_net_ff_obj_ff_classifier is already included in the\n+\t * classifier_size.\n+\t */\n+\tc = kzalloc(classifier_size +\n+\t\t sizeof(struct virtnet_classifier) -\n+\t\t sizeof(struct virtio_net_resource_obj_ff_classifier),\n+\t\t GFP_KERNEL);\n+\tif (!c) {\n+\t\tkfree(key);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tc-\u003esize = classifier_size;\n+\tclassifier = \u0026c-\u003eobj;\n+\tclassifier-\u003ecount = num_hdrs;\n+\tselector = (void *)\u0026classifier-\u003eselectors[0];\n+\n+\tsetup_eth_hdr_key_mask(selector, key, fs, num_hdrs);\n+\n+\tif (has_ipv4(fs-\u003eflow_type) || has_ipv6(fs-\u003eflow_type)) {\n+\t\tkey_offset = selector-\u003elength;\n+\t\tselector = next_selector(selector);\n+\n+\t\terr = setup_ip_key_mask(selector, key + key_offset,\n+\t\t\t\t\tfs, num_hdrs);\n+\t\tif (err)\n+\t\t\tgoto err_classifier;\n+\n+\t\tif (has_udp(fs-\u003eflow_type) || has_tcp(fs-\u003eflow_type)) {\n+\t\t\tkey_offset += selector-\u003elength;\n+\t\t\tselector = next_selector(selector);\n+\n+\t\t\terr = setup_transport_key_mask(selector,\n+\t\t\t\t\t\t key + key_offset,\n+\t\t\t\t\t\t fs);\n+\t\t\tif (err)\n+\t\t\t\tgoto err_classifier;\n+\t\t}\n+\t}\n+\n+\terr = validate_classifier_selectors(ff, classifier, num_hdrs);\n+\tif (err)\n+\t\tgoto err_classifier;\n+\n+\terr = setup_classifier(ff, \u0026c);\n+\tif (err)\n+\t\tgoto err_classifier;\n+\n+\terr = insert_rule(ff, eth_rule, c-\u003eid, key, key_size);\n+\tif (err) {\n+\t\t/* try_destroy_classifier will decrement the refcount on the\n+\t\t * classifier and free it if needed.\n+\t\t */\n+\t\ttry_destroy_classifier(ff, c-\u003eid);\n+\t\tgoto err_key;\n+\t}\n+\n+\treturn 0;\n+\n+err_classifier:\n+\tkfree(c);\n+err_key:\n+\tkfree(key);\n+\n+\treturn err;\n+}\n+\n+static int virtnet_ethtool_flow_insert(struct virtnet_ff *ff,\n+\t\t\t\t struct ethtool_rx_flow_spec *fs,\n+\t\t\t\t u16 curr_queue_pairs)\n+{\n+\tstruct virtnet_ethtool_rule *eth_rule;\n+\tint err;\n+\n+\tif (!ff-\u003eff_supported)\n+\t\treturn -EOPNOTSUPP;\n+\n+\terr = validate_flow_input(ff, fs, curr_queue_pairs);\n+\tif (err)\n+\t\treturn err;\n+\n+\teth_rule = kzalloc(sizeof(*eth_rule), GFP_KERNEL);\n+\tif (!eth_rule)\n+\t\treturn -ENOMEM;\n+\n+\terr = xa_alloc(\u0026ff-\u003eethtool.rules, \u0026fs-\u003elocation, eth_rule,\n+\t\t XA_LIMIT(0, le32_to_cpu(ff-\u003eff_caps-\u003erules_limit) - 1),\n+\t\t GFP_KERNEL);\n+\tif (err)\n+\t\tgoto err_rule;\n+\n+\teth_rule-\u003eflow_spec = *fs;\n+\n+\terr = build_and_insert(ff, eth_rule);\n+\tif (err)\n+\t\tgoto err_xa;\n+\n+\treturn err;\n+\n+err_xa:\n+\txa_erase(\u0026ff-\u003eethtool.rules, eth_rule-\u003eflow_spec.location);\n+\n+err_rule:\n+\tfs-\u003elocation = RX_CLS_LOC_ANY;\n+\tkfree(eth_rule);\n+\n+\treturn err;\n+}\n+\n+static int virtnet_ethtool_flow_remove(struct virtnet_ff *ff, int location)\n+{\n+\tstruct virtnet_ethtool_rule *eth_rule;\n+\tint err = 0;\n+\n+\tif (!ff-\u003eff_supported)\n+\t\treturn -EOPNOTSUPP;\n+\n+\teth_rule = xa_load(\u0026ff-\u003eethtool.rules, location);\n+\tif (!eth_rule) {\n+\t\terr = -ENOENT;\n+\t\tgoto out;\n+\t}\n+\n+\tdestroy_ethtool_rule(ff, eth_rule);\n+out:\n+\treturn err;\n+}\n+\n+static int virtnet_ethtool_get_flow_count(struct virtnet_ff *ff,\n+\t\t\t\t\t struct ethtool_rxnfc *info)\n+{\n+\tif (!ff-\u003eff_supported)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tinfo-\u003erule_cnt = ff-\u003eethtool.num_rules;\n+\tinfo-\u003edata = min(le32_to_cpu(ff-\u003eff_caps-\u003erules_limit),\n+\t\t\t le32_to_cpu(ff-\u003eff_caps-\u003erules_per_group_limit)) |\n+\t\t RX_CLS_LOC_SPECIAL;\n+\n+\treturn 0;\n+}\n+\n+static int virtnet_ethtool_get_flow(struct virtnet_ff *ff,\n+\t\t\t\t struct ethtool_rxnfc *info)\n+{\n+\tstruct virtnet_ethtool_rule *eth_rule;\n+\n+\tif (!ff-\u003eff_supported)\n+\t\treturn -EOPNOTSUPP;\n+\n+\teth_rule = xa_load(\u0026ff-\u003eethtool.rules, info-\u003efs.location);\n+\tif (!eth_rule)\n+\t\treturn -ENOENT;\n+\n+\tinfo-\u003efs = eth_rule-\u003eflow_spec;\n+\n+\treturn 0;\n+}\n+\n+static int\n+virtnet_ethtool_get_all_flows(struct virtnet_ff *ff,\n+\t\t\t struct ethtool_rxnfc *info, u32 *rule_locs)\n+{\n+\tstruct virtnet_ethtool_rule *eth_rule;\n+\tunsigned long i = 0;\n+\tint idx = 0;\n+\n+\tif (!ff-\u003eff_supported)\n+\t\treturn -EOPNOTSUPP;\n+\n+\txa_for_each(\u0026ff-\u003eethtool.rules, i, eth_rule) {\n+\t\tif (idx == info-\u003erule_cnt)\n+\t\t\treturn -EMSGSIZE;\n+\t\trule_locs[idx++] = i;\n+\t}\n+\n+\tinfo-\u003edata = le32_to_cpu(ff-\u003eff_caps-\u003erules_limit);\n+\tinfo-\u003erule_cnt = idx;\n+\n+\treturn 0;\n+}\n+\n+static size_t get_mask_size(u16 type)\n+{\n+\tswitch (type) {\n+\tcase VIRTIO_NET_FF_MASK_TYPE_ETH:\n+\t\treturn sizeof(struct ethhdr);\n+\tcase VIRTIO_NET_FF_MASK_TYPE_IPV4:\n+\t\treturn sizeof(struct iphdr);\n+\tcase VIRTIO_NET_FF_MASK_TYPE_IPV6:\n+\t\treturn sizeof(struct ipv6hdr);\n+\tcase VIRTIO_NET_FF_MASK_TYPE_TCP:\n+\t\treturn sizeof(struct tcphdr);\n+\tcase VIRTIO_NET_FF_MASK_TYPE_UDP:\n+\t\treturn sizeof(struct udphdr);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int virtnet_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info, u32 *rule_locs)\n+{\n+\tstruct virtnet_info *vi = netdev_priv(dev);\n+\tint rc;\n+\n+\tswitch (info-\u003ecmd) {\n+\tcase ETHTOOL_GRXCLSRLCNT:\n+\t\trc = virtnet_ethtool_get_flow_count(\u0026vi-\u003eff, info);\n+\t\tbreak;\n+\tcase ETHTOOL_GRXCLSRULE:\n+\t\trc = virtnet_ethtool_get_flow(\u0026vi-\u003eff, info);\n+\t\tbreak;\n+\tcase ETHTOOL_GRXCLSRLALL:\n+\t\trc = virtnet_ethtool_get_all_flows(\u0026vi-\u003eff, info, rule_locs);\n+\t\tbreak;\n+\tdefault:\n+\t\trc = -EOPNOTSUPP;\n+\t}\n+\n+\treturn rc;\n+}\n+\n+static int virtnet_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info)\n+{\n+\tstruct virtnet_info *vi = netdev_priv(dev);\n+\n+\tswitch (info-\u003ecmd) {\n+\tcase ETHTOOL_SRXCLSRLINS:\n+\t\treturn virtnet_ethtool_flow_insert(\u0026vi-\u003eff, \u0026info-\u003efs,\n+\t\t\t\t\t\t vi-\u003ecurr_queue_pairs);\n+\tcase ETHTOOL_SRXCLSRLDEL:\n+\t\treturn virtnet_ethtool_flow_remove(\u0026vi-\u003eff, info-\u003efs.location);\n+\t}\n+\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static const struct ethtool_ops virtnet_ethtool_ops = {\n+\t.supported_coalesce_params = ETHTOOL_COALESCE_MAX_FRAMES |\n+\t\tETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_USE_ADAPTIVE_RX,\n+\t.get_drvinfo = virtnet_get_drvinfo,\n+\t.get_link = ethtool_op_get_link,\n+\t.get_ringparam = virtnet_get_ringparam,\n+\t.set_ringparam = virtnet_set_ringparam,\n+\t.get_strings = virtnet_get_strings,\n+\t.get_sset_count = virtnet_get_sset_count,\n+\t.get_ethtool_stats = virtnet_get_ethtool_stats,\n+\t.set_channels = virtnet_set_channels,\n+\t.get_channels = virtnet_get_channels,\n+\t.get_ts_info = ethtool_op_get_ts_info,\n+\t.get_link_ksettings = virtnet_get_link_ksettings,\n+\t.set_link_ksettings = virtnet_set_link_ksettings,\n+\t.set_coalesce = virtnet_set_coalesce,\n+\t.get_coalesce = virtnet_get_coalesce,\n+\t.set_per_queue_coalesce = virtnet_set_per_queue_coalesce,\n+\t.get_per_queue_coalesce = virtnet_get_per_queue_coalesce,\n+\t.get_rxfh_key_size = virtnet_get_rxfh_key_size,\n+\t.get_rxfh_indir_size = virtnet_get_rxfh_indir_size,\n+\t.get_rxfh = virtnet_get_rxfh,\n+\t.set_rxfh = virtnet_set_rxfh,\n+\t.get_rxfh_fields = virtnet_get_hashflow,\n+\t.set_rxfh_fields = virtnet_set_hashflow,\n+\t.get_rx_ring_count = virtnet_get_rx_ring_count,\n+\t.get_rxnfc = virtnet_get_rxnfc,\n+\t.set_rxnfc = virtnet_set_rxnfc,\n+};\n+\n+static int virtnet_ff_init(struct virtnet_ff *ff, struct virtio_device *vdev)\n+{\n+\tsize_t ff_mask_size = sizeof(struct virtio_net_ff_cap_mask_data) +\n+\t\t\t sizeof(struct virtio_net_ff_selector) *\n+\t\t\t VIRTIO_NET_FF_MASK_TYPE_MAX;\n+\tstruct virtio_net_resource_obj_ff_group ethtool_group = {};\n+\tstruct virtio_admin_cmd_query_cap_id_result *cap_id_list;\n+\tstruct virtio_net_ff_selector *sel;\n+\tunsigned long sel_types = 0;\n+\tsize_t real_ff_mask_size;\n+\tint err;\n+\tint i;\n+\n+\tif (!vdev-\u003econfig-\u003eadmin_cmd_exec)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tcap_id_list = kzalloc(sizeof(*cap_id_list), GFP_KERNEL);\n+\tif (!cap_id_list)\n+\t\treturn -ENOMEM;\n+\n+\terr = virtio_admin_cap_id_list_query(vdev, cap_id_list);\n+\tif (err)\n+\t\tgoto err_cap_list;\n+\n+\tif (!(virtio_cap_in_list(cap_id_list, VIRTIO_NET_FF_RESOURCE_CAP) \u0026\u0026\n+\t virtio_cap_in_list(cap_id_list, VIRTIO_NET_FF_SELECTOR_CAP) \u0026\u0026\n+\t virtio_cap_in_list(cap_id_list, VIRTIO_NET_FF_ACTION_CAP))) {\n+\t\terr = -EOPNOTSUPP;\n+\t\tgoto err_cap_list;\n+\t}\n+\n+\tff-\u003eff_caps = kzalloc(sizeof(*ff-\u003eff_caps), GFP_KERNEL);\n+\tif (!ff-\u003eff_caps) {\n+\t\terr = -ENOMEM;\n+\t\tgoto err_cap_list;\n+\t}\n+\n+\terr = virtio_admin_cap_get(vdev,\n+\t\t\t\t VIRTIO_NET_FF_RESOURCE_CAP,\n+\t\t\t\t ff-\u003eff_caps,\n+\t\t\t\t sizeof(*ff-\u003eff_caps));\n+\n+\tif (err)\n+\t\tgoto err_ff;\n+\n+\tif (!le32_to_cpu(ff-\u003eff_caps-\u003egroups_limit) ||\n+\t !le32_to_cpu(ff-\u003eff_caps-\u003eclassifiers_limit) ||\n+\t !le32_to_cpu(ff-\u003eff_caps-\u003erules_limit) ||\n+\t !le32_to_cpu(ff-\u003eff_caps-\u003erules_per_group_limit) ||\n+\t !ff-\u003eff_caps-\u003eselectors_per_classifier_limit) {\n+\t\terr = -EINVAL;\n+\t\tgoto err_ff;\n+\t}\n+\n+\t/* VIRTIO_NET_FF_MASK_TYPE start at 1 */\n+\tfor (i = 1; i \u003c= VIRTIO_NET_FF_MASK_TYPE_MAX; i++)\n+\t\tff_mask_size += get_mask_size(i);\n+\n+\tff-\u003eff_mask = kzalloc(ff_mask_size, GFP_KERNEL);\n+\tif (!ff-\u003eff_mask) {\n+\t\terr = -ENOMEM;\n+\t\tgoto err_ff;\n+\t}\n+\n+\terr = virtio_admin_cap_get(vdev,\n+\t\t\t\t VIRTIO_NET_FF_SELECTOR_CAP,\n+\t\t\t\t ff-\u003eff_mask,\n+\t\t\t\t ff_mask_size);\n+\n+\tif (err)\n+\t\tgoto err_ff_mask;\n+\n+\tff-\u003eff_mask-\u003ecount = min_t(u8, ff-\u003eff_mask-\u003ecount,\n+\t\t\t\t VIRTIO_NET_FF_MASK_TYPE_MAX);\n+\n+\tff-\u003eff_actions = kzalloc(sizeof(*ff-\u003eff_actions) +\n+\t\t\t\t\tVIRTIO_NET_FF_ACTION_MAX,\n+\t\t\t\t\tGFP_KERNEL);\n+\tif (!ff-\u003eff_actions) {\n+\t\terr = -ENOMEM;\n+\t\tgoto err_ff_mask;\n+\t}\n+\n+\terr = virtio_admin_cap_get(vdev,\n+\t\t\t\t VIRTIO_NET_FF_ACTION_CAP,\n+\t\t\t\t ff-\u003eff_actions,\n+\t\t\t\t sizeof(*ff-\u003eff_actions) + VIRTIO_NET_FF_ACTION_MAX);\n+\n+\tif (err)\n+\t\tgoto err_ff_action;\n+\n+\tff-\u003eff_actions-\u003ecount = min_t(u8, ff-\u003eff_actions-\u003ecount,\n+\t\t\t\t VIRTIO_NET_FF_ACTION_MAX);\n+\tif (!ff-\u003eff_actions-\u003ecount)\n+\t\tgoto err_ff_action;\n+\n+\tif (le32_to_cpu(ff-\u003eff_caps-\u003egroups_limit) \u003c VIRTNET_FF_MAX_GROUPS) {\n+\t\terr = -ENOSPC;\n+\t\tgoto err_ff_action;\n+\t}\n+\tff-\u003eff_caps-\u003egroups_limit = cpu_to_le32(VIRTNET_FF_MAX_GROUPS);\n+\n+\terr = virtio_admin_cap_set(vdev,\n+\t\t\t\t VIRTIO_NET_FF_RESOURCE_CAP,\n+\t\t\t\t ff-\u003eff_caps,\n+\t\t\t\t sizeof(*ff-\u003eff_caps));\n+\tif (err)\n+\t\tgoto err_ff_action;\n+\n+\treal_ff_mask_size = sizeof(struct virtio_net_ff_cap_mask_data);\n+\tsel = (void *)\u0026ff-\u003eff_mask-\u003eselectors;\n+\n+\tfor (i = 0; i \u003c ff-\u003eff_mask-\u003ecount; i++) {\n+\t\t/* If the selector type is unknown it may indicate the spec\n+\t\t * has been revised to include new types of selectors\n+\t\t */\n+\t\tif (!sel-\u003etype || sel-\u003etype \u003e VIRTIO_NET_FF_MASK_TYPE_MAX)\n+\t\t\tbreak;\n+\n+\t\tif (sel-\u003elength != get_mask_size(sel-\u003etype) ||\n+\t\t test_and_set_bit(sel-\u003etype, \u0026sel_types)) {\n+\t\t\terr = -EPROTO;\n+\t\t\tgoto err_ff_action;\n+\t\t}\n+\t\treal_ff_mask_size += sizeof(*sel) + sel-\u003elength;\n+\t\tif (real_ff_mask_size \u003e ff_mask_size) {\n+\t\t\terr = -EPROTO;\n+\t\t\tgoto err_ff_action;\n+\t\t}\n+\t\tsel = (void *)sel + sizeof(*sel) + sel-\u003elength;\n+\t}\n+\tff-\u003eff_mask-\u003ecount = i;\n+\n+\terr = virtio_admin_cap_set(vdev,\n+\t\t\t\t VIRTIO_NET_FF_SELECTOR_CAP,\n+\t\t\t\t ff-\u003eff_mask,\n+\t\t\t\t real_ff_mask_size);\n+\tif (err)\n+\t\tgoto err_ff_action;\n+\n+\terr = virtio_admin_cap_set(vdev,\n+\t\t\t\t VIRTIO_NET_FF_ACTION_CAP,\n+\t\t\t\t ff-\u003eff_actions,\n+\t\t\t\t sizeof(*ff-\u003eff_actions) + ff-\u003eff_actions-\u003ecount);\n+\tif (err)\n+\t\tgoto err_ff_action;\n+\n+\tethtool_group.group_priority = cpu_to_le16(VIRTNET_FF_ETHTOOL_GROUP_PRIORITY);\n+\n+\t/* Use priority for the object ID. */\n+\terr = virtio_admin_obj_create(vdev,\n+\t\t\t\t VIRTIO_NET_RESOURCE_OBJ_FF_GROUP,\n+\t\t\t\t VIRTNET_FF_ETHTOOL_GROUP_PRIORITY,\n+\t\t\t\t VIRTIO_ADMIN_GROUP_TYPE_SELF,\n+\t\t\t\t 0,\n+\t\t\t\t \u0026ethtool_group,\n+\t\t\t\t sizeof(ethtool_group));\n+\tif (err)\n+\t\tgoto err_ff_action;\n+\n+\txa_init_flags(\u0026ff-\u003eclassifiers, XA_FLAGS_ALLOC);\n+\txa_init_flags(\u0026ff-\u003eethtool.rules, XA_FLAGS_ALLOC);\n+\tff-\u003evdev = vdev;\n+\tff-\u003eff_supported = true;\n+\n+\tkfree(cap_id_list);\n+\n+\treturn 0;\n+\n+err_ff_action:\n+\tkfree(ff-\u003eff_actions);\n+\tff-\u003eff_actions = NULL;\n+err_ff_mask:\n+\tkfree(ff-\u003eff_mask);\n+\tff-\u003eff_mask = NULL;\n+err_ff:\n+\tkfree(ff-\u003eff_caps);\n+\tff-\u003eff_caps = NULL;\n+err_cap_list:\n+\tkfree(cap_id_list);\n+\n+\treturn err;\n+}\n+\n+static void virtnet_ff_cleanup(struct virtnet_ff *ff)\n+{\n+\tstruct virtnet_ethtool_rule *eth_rule;\n+\tunsigned long i;\n+\n+\tif (!ff-\u003eff_supported)\n+\t\treturn;\n+\n+\txa_for_each(\u0026ff-\u003eethtool.rules, i, eth_rule)\n+\t\tdestroy_ethtool_rule(ff, eth_rule);\n+\n+\txa_destroy(\u0026ff-\u003eethtool.rules);\n+\txa_destroy(\u0026ff-\u003eclassifiers);\n+\n+\tvirtio_admin_obj_destroy(ff-\u003evdev,\n+\t\t\t\t VIRTIO_NET_RESOURCE_OBJ_FF_GROUP,\n+\t\t\t\t VIRTNET_FF_ETHTOOL_GROUP_PRIORITY,\n+\t\t\t\t VIRTIO_ADMIN_GROUP_TYPE_SELF,\n+\t\t\t\t 0);\n+\n+\tkfree(ff-\u003eff_actions);\n+\tkfree(ff-\u003eff_mask);\n+\tkfree(ff-\u003eff_caps);\n+\tff-\u003eff_supported = false;\n+}\n+\n+static void virtnet_freeze_down(struct virtio_device *vdev)\n+{\n+\tstruct virtnet_info *vi = vdev-\u003epriv;\n+\n+\t/* Make sure no work handler is accessing the device */\n+\tflush_work(\u0026vi-\u003econfig_work);\n+\tdisable_rx_mode_work(vi);\n+\tflush_work(\u0026vi-\u003erx_mode_work);\n+\n+\tif (netif_running(vi-\u003edev)) {\n+\t\trtnl_lock();\n+\t\tvirtnet_close(vi-\u003edev);\n+\t\trtnl_unlock();\n+\t}\n+\n+\tnetif_tx_lock_bh(vi-\u003edev);\n+\tnetif_device_detach(vi-\u003edev);\n+\tnetif_tx_unlock_bh(vi-\u003edev);\n+\n+\trtnl_lock();\n+\tvirtnet_ff_cleanup(\u0026vi-\u003eff);\n+\trtnl_unlock();\n+}\n+\n+static int init_vqs(struct virtnet_info *vi);\n+\n+static int virtnet_restore_up(struct virtio_device *vdev)\n+{\n+\tstruct virtnet_info *vi = vdev-\u003epriv;\n+\tint err;\n+\n+\terr = init_vqs(vi);\n+\tif (err)\n+\t\treturn err;\n+\n+\terr = virtnet_create_page_pools(vi);\n+\tif (err)\n+\t\tgoto err_del_vqs;\n+\n+\tvirtio_device_ready(vdev);\n+\n+\tenable_rx_mode_work(vi);\n+\n+\tif (netif_running(vi-\u003edev)) {\n+\t\trtnl_lock();\n+\t\terr = virtnet_open(vi-\u003edev);\n+\t\trtnl_unlock();\n+\t\tif (err)\n+\t\t\tgoto err_destroy_pools;\n+\t}\n+\n+\t/*\n+\t * Initialize flow filters. Not supported is an acceptable and common\n+\t * return code\n+\t */\n+\trtnl_lock();\n+\terr = virtnet_ff_init(\u0026vi-\u003eff, vi-\u003evdev);\n+\tif (err \u0026\u0026 err != -EOPNOTSUPP) {\n+\t\trtnl_unlock();\n+\t\tvirtnet_freeze_down(vi-\u003evdev);\n+\t\tremove_vq_common(vi);\n+\t\treturn err;\n+\t}\n+\trtnl_unlock();\n+\n+\tnetif_tx_lock_bh(vi-\u003edev);\n+\tnetif_device_attach(vi-\u003edev);\n+\tnetif_tx_unlock_bh(vi-\u003edev);\n+\n+\treturn 0;\n+\n+err_destroy_pools:\n+\tvirtio_reset_device(vdev);\n+\tfree_unused_bufs(vi);\n+\tvirtnet_destroy_page_pools(vi);\n+\tvirtnet_del_vqs(vi);\n+\treturn err;\n+\n+err_del_vqs:\n+\tvirtio_reset_device(vdev);\n+\tvirtnet_del_vqs(vi);\n+\treturn err;\n+}\n+\n+static int virtnet_set_guest_offloads(struct virtnet_info *vi, u64 offloads)\n+{\n+\t__virtio64 *_offloads __free(kfree) = NULL;\n+\tstruct scatterlist sg;\n+\n+\t_offloads = kzalloc_obj(*_offloads);\n+\tif (!_offloads)\n+\t\treturn -ENOMEM;\n+\n+\t*_offloads = cpu_to_virtio64(vi-\u003evdev, offloads);\n+\n+\tsg_init_one(\u0026sg, _offloads, sizeof(*_offloads));\n+\n+\tif (!virtnet_send_command(vi, VIRTIO_NET_CTRL_GUEST_OFFLOADS,\n+\t\t\t\t VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, \u0026sg)) {\n+\t\tdev_warn(\u0026vi-\u003edev-\u003edev, \"Fail to set guest offload.\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int virtnet_clear_guest_offloads(struct virtnet_info *vi)\n+{\n+\tu64 offloads = 0;\n+\n+\tif (!vi-\u003eguest_offloads)\n+\t\treturn 0;\n+\n+\treturn virtnet_set_guest_offloads(vi, offloads);\n+}\n+\n+static int virtnet_restore_guest_offloads(struct virtnet_info *vi)\n+{\n+\tu64 offloads = vi-\u003eguest_offloads;\n+\n+\tif (!vi-\u003eguest_offloads)\n+\t\treturn 0;\n+\n+\treturn virtnet_set_guest_offloads(vi, offloads);\n+}\n+\n+static int virtnet_rq_bind_xsk_pool(struct virtnet_info *vi, struct receive_queue *rq,\n+\t\t\t\t struct xsk_buff_pool *pool)\n+{\n+\tint err, qindex;\n+\n+\tqindex = rq - vi-\u003erq;\n+\n+\tif (pool) {\n+\t\terr = xdp_rxq_info_reg(\u0026rq-\u003exsk_rxq_info, vi-\u003edev, qindex, rq-\u003enapi.napi_id);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\n+\t\terr = xdp_rxq_info_reg_mem_model(\u0026rq-\u003exsk_rxq_info,\n+\t\t\t\t\t\t MEM_TYPE_XSK_BUFF_POOL, NULL);\n+\t\tif (err \u003c 0)\n+\t\t\tgoto unreg;\n+\n+\t\txsk_pool_set_rxq_info(pool, \u0026rq-\u003exsk_rxq_info);\n+\t}\n+\n+\tvirtnet_rx_pause(vi, rq);\n+\n+\terr = virtqueue_reset(rq-\u003evq, virtnet_rq_unmap_free_buf, NULL);\n+\tif (err) {\n+\t\tnetdev_err(vi-\u003edev, \"reset rx fail: rx queue index: %d err: %d\\n\", qindex, err);\n+\n+\t\tpool = NULL;\n+\t}\n+\n+\trq-\u003exsk_pool = pool;\n+\n+\tvirtnet_rx_resume(vi, rq, true);\n+\n+\tif (pool)\n+\t\treturn 0;\n+\n+unreg:\n+\txdp_rxq_info_unreg(\u0026rq-\u003exsk_rxq_info);\n+\treturn err;\n+}\n+\n+static int virtnet_sq_bind_xsk_pool(struct virtnet_info *vi,\n+\t\t\t\t struct send_queue *sq,\n+\t\t\t\t struct xsk_buff_pool *pool)\n+{\n+\tint err, qindex;\n+\n+\tqindex = sq - vi-\u003esq;\n+\n+\tvirtnet_tx_pause(vi, sq);\n+\n+\terr = virtqueue_reset(sq-\u003evq, virtnet_sq_free_unused_buf,\n+\t\t\t virtnet_sq_free_unused_buf_done);\n+\tif (err) {\n+\t\tnetdev_err(vi-\u003edev, \"reset tx fail: tx queue index: %d err: %d\\n\", qindex, err);\n+\t\tpool = NULL;\n+\t}\n+\n+\tsq-\u003exsk_pool = pool;\n+\n+\tvirtnet_tx_resume(vi, sq);\n+\n+\treturn err;\n+}\n+\n+static int virtnet_xsk_pool_enable(struct net_device *dev,\n+\t\t\t\t struct xsk_buff_pool *pool,\n+\t\t\t\t u16 qid)\n+{\n+\tstruct virtnet_info *vi = netdev_priv(dev);\n+\tstruct receive_queue *rq;\n+\tstruct device *dma_dev;\n+\tstruct send_queue *sq;\n+\tdma_addr_t hdr_dma;\n+\tint err, size;\n+\n+\tif (vi-\u003ehdr_len \u003e xsk_pool_get_headroom(pool))\n+\t\treturn -EINVAL;\n+\n+\t/* In big_packets mode, xdp cannot work, so there is no need to\n+\t * initialize xsk of rq.\n+\t */\n+\tif (!vi-\u003erq[qid].page_pool)\n+\t\treturn -ENOENT;\n+\n+\tif (qid \u003e= vi-\u003ecurr_queue_pairs)\n+\t\treturn -EINVAL;\n+\n+\tsq = \u0026vi-\u003esq[qid];\n+\trq = \u0026vi-\u003erq[qid];\n+\n+\t/* xsk assumes that tx and rx must have the same dma device. The af-xdp\n+\t * may use one buffer to receive from the rx and reuse this buffer to\n+\t * send by the tx. So the dma dev of sq and rq must be the same one.\n+\t *\n+\t * But vq-\u003edma_dev allows every vq has the respective dma dev. So I\n+\t * check the dma dev of vq and sq is the same dev.\n+\t */\n+\tif (virtqueue_dma_dev(rq-\u003evq) != virtqueue_dma_dev(sq-\u003evq))\n+\t\treturn -EINVAL;\n \n \tdma_dev = virtqueue_dma_dev(rq-\u003evq);\n \tif (!dma_dev)\n@@ -7043,6 +8255,15 @@ static int virtnet_probe(struct virtio_device *vdev)\n \n \tvirtio_device_ready(vdev);\n \n+\t/* Initialize flow filters. Not supported is an acceptable and common\n+\t * return code\n+\t */\n+\terr = virtnet_ff_init(\u0026vi-\u003eff, vi-\u003evdev);\n+\tif (err \u0026\u0026 err != -EOPNOTSUPP) {\n+\t\trtnl_unlock();\n+\t\tgoto free_unregister_netdev;\n+\t}\n+\n \tif (vi-\u003ehas_rss || vi-\u003ehas_rss_hash_report) {\n \t\tif (!virtnet_commit_rss_command(vi)) {\n \t\t\tdev_warn(\u0026vdev-\u003edev, \"RSS disabled because committing failed.\\n\");\n@@ -7125,6 +8346,7 @@ static int virtnet_probe(struct virtio_device *vdev)\n \n free_unregister_netdev:\n \tunregister_netdev(dev);\n+\tvirtnet_ff_cleanup(\u0026vi-\u003eff);\n free_failover:\n \tnet_failover_destroy(vi-\u003efailover);\n free_page_pools:\n@@ -7175,6 +8397,7 @@ static void virtnet_remove(struct virtio_device *vdev)\n \tvirtnet_free_irq_moder(vi);\n \n \tunregister_netdev(vi-\u003edev);\n+\tvirtnet_ff_cleanup(\u0026vi-\u003eff);\n \n \tnet_failover_destroy(vi-\u003efailover);\n \ndiff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile\nindex eefcfe90d6b8b..2b4a204dde331 100644\n--- a/drivers/virtio/Makefile\n+++ b/drivers/virtio/Makefile\n@@ -1,5 +1,5 @@\n # SPDX-License-Identifier: GPL-2.0\n-obj-$(CONFIG_VIRTIO) += virtio.o virtio_ring.o\n+obj-$(CONFIG_VIRTIO) += virtio.o virtio_ring.o virtio_admin_commands.o\n obj-$(CONFIG_VIRTIO_ANCHOR) += virtio_anchor.o\n obj-$(CONFIG_VIRTIO_PCI_LIB) += virtio_pci_modern_dev.o\n obj-$(CONFIG_VIRTIO_PCI_LIB_LEGACY) += virtio_pci_legacy_dev.o\ndiff --git a/drivers/virtio/virtio_admin_commands.c b/drivers/virtio/virtio_admin_commands.c\nnew file mode 100644\nindex 0000000000000..08b41f38baea4\n--- /dev/null\n+++ b/drivers/virtio/virtio_admin_commands.c\n@@ -0,0 +1,173 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/virtio.h\u003e\n+#include \u003clinux/virtio_config.h\u003e\n+#include \u003clinux/virtio_admin.h\u003e\n+#include \u003clinux/overflow.h\u003e\n+#include \u003cuapi/linux/virtio_pci.h\u003e\n+\n+int virtio_admin_cap_id_list_query(struct virtio_device *vdev,\n+\t\t\t\t struct virtio_admin_cmd_query_cap_id_result *data)\n+{\n+\tstruct virtio_admin_cmd cmd = {};\n+\tstruct scatterlist result_sg;\n+\n+\tif (!vdev-\u003econfig-\u003eadmin_cmd_exec)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tsg_init_one(\u0026result_sg, data, sizeof(*data));\n+\tcmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_CAP_ID_LIST_QUERY);\n+\tcmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SELF);\n+\tcmd.result_sg = \u0026result_sg;\n+\n+\treturn vdev-\u003econfig-\u003eadmin_cmd_exec(vdev, \u0026cmd);\n+}\n+EXPORT_SYMBOL_GPL(virtio_admin_cap_id_list_query);\n+\n+int virtio_admin_cap_get(struct virtio_device *vdev,\n+\t\t\t u16 id,\n+\t\t\t void *caps,\n+\t\t\t size_t cap_size)\n+{\n+\tstruct virtio_admin_cmd_cap_get_data *data;\n+\tstruct virtio_admin_cmd cmd = {};\n+\tstruct scatterlist result_sg;\n+\tstruct scatterlist data_sg;\n+\tint err;\n+\n+\tif (!vdev-\u003econfig-\u003eadmin_cmd_exec)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tdata = kzalloc_obj(*data);\n+\tif (!data)\n+\t\treturn -ENOMEM;\n+\n+\tdata-\u003eid = cpu_to_le16(id);\n+\tsg_init_one(\u0026data_sg, data, sizeof(*data));\n+\tsg_init_one(\u0026result_sg, caps, cap_size);\n+\tcmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_DEVICE_CAP_GET);\n+\tcmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SELF);\n+\tcmd.data_sg = \u0026data_sg;\n+\tcmd.result_sg = \u0026result_sg;\n+\n+\terr = vdev-\u003econfig-\u003eadmin_cmd_exec(vdev, \u0026cmd);\n+\tkfree(data);\n+\n+\treturn err;\n+}\n+EXPORT_SYMBOL_GPL(virtio_admin_cap_get);\n+\n+int virtio_admin_cap_set(struct virtio_device *vdev,\n+\t\t\t u16 id,\n+\t\t\t const void *caps,\n+\t\t\t size_t cap_size)\n+{\n+\tstruct virtio_admin_cmd_cap_set_data *data;\n+\tstruct virtio_admin_cmd cmd = {};\n+\tstruct scatterlist data_sg;\n+\tsize_t data_size;\n+\tint err;\n+\n+\tif (!vdev-\u003econfig-\u003eadmin_cmd_exec)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (check_add_overflow(sizeof(*data), cap_size, \u0026data_size))\n+\t\treturn -EOVERFLOW;\n+\n+\tdata = kzalloc(data_size, GFP_KERNEL);\n+\tif (!data)\n+\t\treturn -ENOMEM;\n+\n+\tdata-\u003eid = cpu_to_le16(id);\n+\tmemcpy(data-\u003ecap_specific_data, caps, cap_size);\n+\tsg_init_one(\u0026data_sg, data, data_size);\n+\tcmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_DRIVER_CAP_SET);\n+\tcmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SELF);\n+\tcmd.data_sg = \u0026data_sg;\n+\tcmd.result_sg = NULL;\n+\n+\terr = vdev-\u003econfig-\u003eadmin_cmd_exec(vdev, \u0026cmd);\n+\tkfree(data);\n+\n+\treturn err;\n+}\n+EXPORT_SYMBOL_GPL(virtio_admin_cap_set);\n+\n+int virtio_admin_obj_create(struct virtio_device *vdev,\n+\t\t\t u16 obj_type,\n+\t\t\t u32 obj_id,\n+\t\t\t u16 group_type,\n+\t\t\t u64 group_member_id,\n+\t\t\t const void *obj_specific_data,\n+\t\t\t size_t obj_specific_data_size)\n+{\n+\tsize_t data_size = sizeof(struct virtio_admin_cmd_resource_obj_create_data);\n+\tstruct virtio_admin_cmd_resource_obj_create_data *obj_create_data;\n+\tstruct virtio_admin_cmd cmd = {};\n+\tstruct scatterlist data_sg;\n+\tvoid *data;\n+\tint err;\n+\n+\tif (!vdev-\u003econfig-\u003eadmin_cmd_exec)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (check_add_overflow(data_size, obj_specific_data_size, \u0026data_size))\n+\t\treturn -EOVERFLOW;\n+\n+\tdata = kzalloc(data_size, GFP_KERNEL);\n+\tif (!data)\n+\t\treturn -ENOMEM;\n+\n+\tobj_create_data = data;\n+\tobj_create_data-\u003ehdr.type = cpu_to_le16(obj_type);\n+\tobj_create_data-\u003ehdr.id = cpu_to_le32(obj_id);\n+\tmemcpy(obj_create_data-\u003eresource_obj_specific_data, obj_specific_data,\n+\t obj_specific_data_size);\n+\tsg_init_one(\u0026data_sg, data, data_size);\n+\n+\tcmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_RESOURCE_OBJ_CREATE);\n+\tcmd.group_type = cpu_to_le16(group_type);\n+\tcmd.group_member_id = cpu_to_le64(group_member_id);\n+\tcmd.data_sg = \u0026data_sg;\n+\n+\terr = vdev-\u003econfig-\u003eadmin_cmd_exec(vdev, \u0026cmd);\n+\tkfree(data);\n+\n+\treturn err;\n+}\n+EXPORT_SYMBOL_GPL(virtio_admin_obj_create);\n+\n+void virtio_admin_obj_destroy(struct virtio_device *vdev,\n+\t\t\t u16 obj_type,\n+\t\t\t u32 obj_id,\n+\t\t\t u16 group_type,\n+\t\t\t u64 group_member_id)\n+{\n+\tstruct virtio_admin_cmd_resource_obj_cmd_hdr *data;\n+\tstruct virtio_admin_cmd cmd = {};\n+\tstruct scatterlist data_sg;\n+\tint err;\n+\n+\tif (!vdev-\u003econfig-\u003eadmin_cmd_exec)\n+\t\treturn;\n+\n+\tdata = kzalloc_obj(*data);\n+\tif (WARN_ON(!data))\n+\t\treturn;\n+\n+\tdata-\u003etype = cpu_to_le16(obj_type);\n+\tdata-\u003eid = cpu_to_le32(obj_id);\n+\tsg_init_one(\u0026data_sg, data, sizeof(*data));\n+\tcmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_RESOURCE_OBJ_DESTROY);\n+\tcmd.group_type = cpu_to_le16(group_type);\n+\tcmd.group_member_id = cpu_to_le64(group_member_id);\n+\tcmd.data_sg = \u0026data_sg;\n+\n+\terr = vdev-\u003econfig-\u003eadmin_cmd_exec(vdev, \u0026cmd);\n+\tkfree(data);\n+\n+\tWARN_ON_ONCE(err);\n+}\n+EXPORT_SYMBOL_GPL(virtio_admin_obj_destroy);\ndiff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c\nindex 164f480b18a6f..ec0c92c782d6e 100644\n--- a/drivers/virtio/virtio_pci_common.c\n+++ b/drivers/virtio/virtio_pci_common.c\n@@ -256,6 +256,11 @@ static void vp_del_vq(struct virtqueue *vq, struct virtio_pci_vq_info *info)\n \t\tspin_unlock_irqrestore(\u0026vp_dev-\u003elock, flags);\n \t}\n \n+\tif (vp_is_avq(vq-\u003evdev, vq-\u003eindex)) {\n+\t\tcancel_work_sync(\u0026vp_dev-\u003eadmin_vq.work);\n+\t\tvp_dev-\u003eadmin_vq.info = NULL;\n+\t}\n+\n \tvp_dev-\u003edel_vq(info);\n \tkfree(info);\n }\ndiff --git a/drivers/virtio/virtio_pci_common.h b/drivers/virtio/virtio_pci_common.h\nindex 8cd01de27bafe..8b0178295342f 100644\n--- a/drivers/virtio/virtio_pci_common.h\n+++ b/drivers/virtio/virtio_pci_common.h\n@@ -30,6 +30,7 @@\n #include \u003clinux/highmem.h\u003e\n #include \u003clinux/spinlock.h\u003e\n #include \u003clinux/mutex.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n \n struct virtio_pci_vq_info {\n \t/* the actual virtqueue */\n@@ -46,9 +47,10 @@ struct virtio_pci_admin_vq {\n \t/* Virtqueue info associated with this admin queue. */\n \tstruct virtio_pci_vq_info *info;\n \t/* Protects virtqueue access. */\n-\tspinlock_t lock;\n+\tstruct mutex lock;\n+\t/* Admin command completion work. */\n+\tstruct work_struct work;\n \tu64 supported_cmds;\n-\tu64 supported_caps;\n \tu8 max_dev_parts_objects;\n \tstruct ida dev_parts_ida;\n \t/* Name of the admin queue: avq.$vq_index. */\ndiff --git a/drivers/virtio/virtio_pci_modern.c b/drivers/virtio/virtio_pci_modern.c\nindex 6d8ae2a6a8caf..3b2515e29728a 100644\n--- a/drivers/virtio/virtio_pci_modern.c\n+++ b/drivers/virtio/virtio_pci_modern.c\n@@ -47,13 +47,20 @@ static int vp_avq_index(struct virtio_device *vdev, u16 *index, u16 *num)\n void vp_modern_avq_done(struct virtqueue *vq)\n {\n \tstruct virtio_pci_device *vp_dev = to_vp_device(vq-\u003evdev);\n-\tstruct virtio_pci_admin_vq *admin_vq = \u0026vp_dev-\u003eadmin_vq;\n+\n+\tschedule_work(\u0026vp_dev-\u003eadmin_vq.work);\n+}\n+\n+static void vp_modern_avq_work(struct work_struct *work)\n+{\n \tunsigned int status_size = sizeof(struct virtio_admin_cmd_status);\n+\tstruct virtio_pci_admin_vq *admin_vq =\n+\t\tcontainer_of(work, struct virtio_pci_admin_vq, work);\n+\tstruct virtqueue *vq = admin_vq-\u003einfo-\u003evq;\n \tstruct virtio_admin_cmd *cmd;\n-\tunsigned long flags;\n \tunsigned int len;\n \n-\tspin_lock_irqsave(\u0026admin_vq-\u003elock, flags);\n+\tmutex_lock(\u0026admin_vq-\u003elock);\n \tdo {\n \t\tvirtqueue_disable_cb(vq);\n \t\twhile ((cmd = virtqueue_get_buf(vq, \u0026len))) {\n@@ -71,7 +78,7 @@ void vp_modern_avq_done(struct virtqueue *vq)\n \t\t\tcomplete(\u0026cmd-\u003ecompletion);\n \t\t}\n \t} while (!virtqueue_enable_cb(vq));\n-\tspin_unlock_irqrestore(\u0026admin_vq-\u003elock, flags);\n+\tmutex_unlock(\u0026admin_vq-\u003elock);\n }\n \n static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,\n@@ -82,7 +89,6 @@ static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,\n \t\t\t\t struct virtio_admin_cmd *cmd)\n {\n \tstruct virtqueue *vq;\n-\tunsigned long flags;\n \tint ret;\n \n \tvq = admin_vq-\u003einfo-\u003evq;\n@@ -100,11 +106,11 @@ static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,\n \tif (virtqueue_is_broken(vq))\n \t\treturn -EIO;\n \n-\tspin_lock_irqsave(\u0026admin_vq-\u003elock, flags);\n+\tmutex_lock(\u0026admin_vq-\u003elock);\n \tret = virtqueue_add_sgs(vq, sgs, out_num, in_num, cmd, GFP_KERNEL);\n \tif (ret \u003c 0) {\n \t\tif (ret == -ENOSPC) {\n-\t\t\tspin_unlock_irqrestore(\u0026admin_vq-\u003elock, flags);\n+\t\t\tmutex_unlock(\u0026admin_vq-\u003elock);\n \t\t\tcpu_relax();\n \t\t\tgoto again;\n \t\t}\n@@ -112,14 +118,14 @@ static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,\n \t}\n \tif (!virtqueue_kick(vq))\n \t\tgoto unlock_err;\n-\tspin_unlock_irqrestore(\u0026admin_vq-\u003elock, flags);\n+\tmutex_unlock(\u0026admin_vq-\u003elock);\n \n \twait_for_completion(\u0026cmd-\u003ecompletion);\n \n \treturn cmd-\u003eret;\n \n unlock_err:\n-\tspin_unlock_irqrestore(\u0026admin_vq-\u003elock, flags);\n+\tmutex_unlock(\u0026admin_vq-\u003elock);\n \treturn -EIO;\n }\n \n@@ -304,10 +310,10 @@ virtio_pci_admin_cmd_dev_parts_objects_enable(struct virtio_device *virtio_dev)\n \n static void virtio_pci_admin_cmd_cap_init(struct virtio_device *virtio_dev)\n {\n-\tstruct virtio_pci_device *vp_dev = to_vp_device(virtio_dev);\n \tstruct virtio_admin_cmd_query_cap_id_result *data;\n \tstruct virtio_admin_cmd cmd = {};\n \tstruct scatterlist result_sg;\n+\tu64 caps;\n \tint ret;\n \n \tdata = kzalloc_obj(*data);\n@@ -323,12 +329,8 @@ static void virtio_pci_admin_cmd_cap_init(struct virtio_device *virtio_dev)\n \tif (ret)\n \t\tgoto end;\n \n-\t/* Max number of caps fits into a single u64 */\n-\tBUILD_BUG_ON(sizeof(data-\u003esupported_caps) \u003e sizeof(u64));\n-\n-\tvp_dev-\u003eadmin_vq.supported_caps = le64_to_cpu(data-\u003esupported_caps[0]);\n-\n-\tif (!(vp_dev-\u003eadmin_vq.supported_caps \u0026 (1 \u003c\u003c VIRTIO_DEV_PARTS_CAP)))\n+\tcaps = le64_to_cpu(data-\u003esupported_caps[0]);\n+\tif (!(caps \u0026 BIT_ULL(VIRTIO_DEV_PARTS_CAP)))\n \t\tgoto end;\n \n \tvirtio_pci_admin_cmd_dev_parts_objects_enable(virtio_dev);\n@@ -354,6 +356,8 @@ static void vp_modern_avq_cleanup(struct virtio_device *vdev)\n \tif (!virtio_has_feature(vdev, VIRTIO_F_ADMIN_VQ))\n \t\treturn;\n \n+\tcancel_work_sync(\u0026vp_dev-\u003eadmin_vq.work);\n+\n \tvq = vp_dev-\u003eadmin_vq.info-\u003evq;\n \tif (!vq)\n \t\treturn;\n@@ -558,10 +562,12 @@ static void vp_reset(struct virtio_device *vdev)\n \twhile (vp_modern_get_status(mdev))\n \t\tmsleep(1);\n \n-\tvp_modern_avq_cleanup(vdev);\n-\n-\t/* Flush pending VQ/configuration callbacks. */\n+\t/* Flush pending VQ/configuration callbacks before cleanup, so that\n+\t * vp_modern_avq_done() can no longer schedule admin_vq.work.\n+\t */\n \tvp_synchronize_vectors(vdev);\n+\n+\tvp_modern_avq_cleanup(vdev);\n }\n \n static int vp_active_vq(struct virtqueue *vq, u16 msix_vec)\n@@ -1242,6 +1248,7 @@ static const struct virtio_config_ops virtio_pci_config_nodev_ops = {\n \t.get_shm_region = vp_get_shm_region,\n \t.disable_vq_and_reset = vp_modern_disable_vq_and_reset,\n \t.enable_vq_after_reset = vp_modern_enable_vq_after_reset,\n+\t.admin_cmd_exec = vp_modern_admin_cmd_exec,\n };\n \n static const struct virtio_config_ops virtio_pci_config_ops = {\n@@ -1262,6 +1269,7 @@ static const struct virtio_config_ops virtio_pci_config_ops = {\n \t.get_shm_region = vp_get_shm_region,\n \t.disable_vq_and_reset = vp_modern_disable_vq_and_reset,\n \t.enable_vq_after_reset = vp_modern_enable_vq_after_reset,\n+\t.admin_cmd_exec = vp_modern_admin_cmd_exec,\n };\n \n /* the PCI probing function */\n@@ -1288,8 +1296,9 @@ int virtio_pci_modern_probe(struct virtio_pci_device *vp_dev)\n \tvp_dev-\u003eavq_index = vp_avq_index;\n \tvp_dev-\u003eisr = mdev-\u003eisr;\n \tvp_dev-\u003evdev.id = mdev-\u003eid;\n+\tmutex_init(\u0026vp_dev-\u003eadmin_vq.lock);\n+\tINIT_WORK(\u0026vp_dev-\u003eadmin_vq.work, vp_modern_avq_work);\n \n-\tspin_lock_init(\u0026vp_dev-\u003eadmin_vq.lock);\n \treturn 0;\n }\n \ndiff --git a/include/linux/ethtool.h b/include/linux/ethtool.h\nindex 12683b5d125e4..adecb9225ee3c 100644\n--- a/include/linux/ethtool.h\n+++ b/include/linux/ethtool.h\n@@ -1567,4 +1567,10 @@ struct ethtool_forced_speed_map {\n \n void\n ethtool_forced_speed_maps_init(struct ethtool_forced_speed_map *maps, u32 size);\n+\n+static inline u32 ethtool_flow_type_mask(u32 flow_type)\n+{\n+\treturn flow_type \u0026 ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS);\n+}\n+\n #endif /* _LINUX_ETHTOOL_H */\ndiff --git a/include/linux/virtio_admin.h b/include/linux/virtio_admin.h\nnew file mode 100644\nindex 0000000000000..9095a04814956\n--- /dev/null\n+++ b/include/linux/virtio_admin.h\n@@ -0,0 +1,130 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Header file for virtio admin operations\n+ */\n+\n+#ifndef _LINUX_VIRTIO_ADMIN_H\n+#define _LINUX_VIRTIO_ADMIN_H\n+\n+#include \u003clinux/bug.h\u003e\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/byteorder/generic.h\u003e\n+#include \u003cuapi/linux/virtio_pci.h\u003e\n+\n+struct virtio_device;\n+\n+/**\n+ * virtio_cap_in_list - Check if a capability is supported in the capability list\n+ * @cap_list: Pointer to capability list structure containing supported_caps array\n+ * @cap: Capability ID to check\n+ *\n+ * The cap_list contains a supported_caps array of little-endian 64-bit integers\n+ * where each bit represents a capability. Bit 0 of the first element represents\n+ * capability ID 0, bit 1 represents capability ID 1, and so on.\n+ *\n+ * Return: true if capability is supported, false otherwise\n+ */\n+static inline bool virtio_cap_in_list(\n+\tconst struct virtio_admin_cmd_query_cap_id_result *cap_list, u16 cap)\n+{\n+\tBUILD_BUG_ON(cap \u003e VIRTIO_ADMIN_MAX_CAP);\n+\treturn !!(1 \u0026 (le64_to_cpu(cap_list-\u003esupported_caps[cap / 64]) \u003e\u003e\n+\t\t (cap % 64)));\n+}\n+\n+/**\n+ * virtio_admin_cap_id_list_query - Query the list of available capability IDs\n+ * @vdev: The virtio device to query\n+ * @data: Pointer to result structure (must be zero-initialized and heap allocated)\n+ *\n+ * This function queries the virtio device for the list of available capability\n+ * IDs that can be used with virtio_admin_cap_get() and virtio_admin_cap_set().\n+ * The result is stored in the provided data structure.\n+ *\n+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin\n+ * operations or capability queries, or a negative error code on other failures.\n+ */\n+int virtio_admin_cap_id_list_query(struct virtio_device *vdev,\n+\t\t\t\t struct virtio_admin_cmd_query_cap_id_result *data);\n+\n+/**\n+ * virtio_admin_cap_get - Get capability data for a specific capability ID\n+ * @vdev: The virtio device\n+ * @id: Capability ID to retrieve\n+ * @caps: Pointer to capability data structure (must be heap allocated)\n+ * @cap_size: Size of the capability data structure\n+ *\n+ * This function retrieves a specific capability from the virtio device.\n+ * The capability data is stored in the provided buffer. The caller must\n+ * ensure the buffer is large enough to hold the capability data.\n+ *\n+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin\n+ * operations or capability retrieval, or a negative error code on other failures.\n+ */\n+int virtio_admin_cap_get(struct virtio_device *vdev,\n+\t\t\t u16 id,\n+\t\t\t void *caps,\n+\t\t\t size_t cap_size);\n+\n+/**\n+ * virtio_admin_cap_set - Set capability data for a specific capability ID\n+ * @vdev: The virtio device\n+ * @id: Capability ID to set\n+ * @caps: Pointer to capability data structure (must be heap allocated)\n+ * @cap_size: Size of the capability data structure\n+ *\n+ * This function sets a specific capability on the virtio device.\n+ * The capability data is read from the provided buffer and applied\n+ * to the device. The device may validate the capability data before\n+ * applying it.\n+ *\n+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin\n+ * operations or capability setting, or a negative error code on other failures.\n+ */\n+int virtio_admin_cap_set(struct virtio_device *vdev,\n+\t\t\t u16 id,\n+\t\t\t const void *caps,\n+\t\t\t size_t cap_size);\n+\n+/**\n+ * virtio_admin_obj_create - Create an object on a virtio device\n+ * @vdev: the virtio device\n+ * @obj_type: type of object to create\n+ * @obj_id: ID for the new object\n+ * @group_type: administrative group type for the operation\n+ * @group_member_id: member identifier within the administrative group\n+ * @obj_specific_data: object-specific data for creation\n+ * @obj_specific_data_size: size of the object-specific data in bytes\n+ *\n+ * Creates a new object on the virtio device with the specified type and ID.\n+ * The object may require object-specific data for proper initialization.\n+ *\n+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin\n+ * operations or object creation, or a negative error code on other failures.\n+ */\n+int virtio_admin_obj_create(struct virtio_device *vdev,\n+\t\t\t u16 obj_type,\n+\t\t\t u32 obj_id,\n+\t\t\t u16 group_type,\n+\t\t\t u64 group_member_id,\n+\t\t\t const void *obj_specific_data,\n+\t\t\t size_t obj_specific_data_size);\n+\n+/**\n+ * virtio_admin_obj_destroy - Destroy an object on a virtio device\n+ * @vdev: the virtio device\n+ * @obj_type: type of object to destroy\n+ * @obj_id: ID of the object to destroy\n+ * @group_type: administrative group type for the operation\n+ * @group_member_id: member identifier within the administrative group\n+ *\n+ * Destroys an existing object on the virtio device with the specified type\n+ * and ID.\n+ */\n+void virtio_admin_obj_destroy(struct virtio_device *vdev,\n+\t\t\t u16 obj_type,\n+\t\t\t u32 obj_id,\n+\t\t\t u16 group_type,\n+\t\t\t u64 group_member_id);\n+\n+#endif /* _LINUX_VIRTIO_ADMIN_H */\ndiff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h\nindex 69f84ea85d71a..e36a32e0a20c8 100644\n--- a/include/linux/virtio_config.h\n+++ b/include/linux/virtio_config.h\n@@ -108,6 +108,10 @@ struct virtqueue_info {\n *\tReturns 0 on success or error status\n *\tIf disable_vq_and_reset is set, then enable_vq_after_reset must also be\n *\tset.\n+ * @admin_cmd_exec: Execute an admin VQ command (optional).\n+ *\tvdev: the virtio_device\n+ *\tcmd: the command to execute\n+ *\tReturns 0 on success or error status\n */\n struct virtio_config_ops {\n \tvoid (*get)(struct virtio_device *vdev, unsigned offset,\n@@ -137,6 +141,8 @@ struct virtio_config_ops {\n \t\t\t struct virtio_shm_region *region, u8 id);\n \tint (*disable_vq_and_reset)(struct virtqueue *vq);\n \tint (*enable_vq_after_reset)(struct virtqueue *vq);\n+\tint (*admin_cmd_exec)(struct virtio_device *vdev,\n+\t\t\t struct virtio_admin_cmd *cmd);\n };\n \n /**\ndiff --git a/include/uapi/linux/virtio_net_ff.h b/include/uapi/linux/virtio_net_ff.h\nnew file mode 100644\nindex 0000000000000..9152021c99baa\n--- /dev/null\n+++ b/include/uapi/linux/virtio_net_ff.h\n@@ -0,0 +1,156 @@\n+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note\n+ *\n+ * Header file for virtio_net flow filters\n+ */\n+#ifndef _LINUX_VIRTIO_NET_FF_H\n+#define _LINUX_VIRTIO_NET_FF_H\n+\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/stddef.h\u003e\n+\n+#define VIRTIO_NET_FF_RESOURCE_CAP 0x800\n+#define VIRTIO_NET_FF_SELECTOR_CAP 0x801\n+#define VIRTIO_NET_FF_ACTION_CAP 0x802\n+\n+#define VIRTIO_NET_RESOURCE_OBJ_FF_GROUP 0x0200\n+#define VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER 0x0201\n+#define VIRTIO_NET_RESOURCE_OBJ_FF_RULE 0x0202\n+\n+/**\n+ * struct virtio_net_ff_cap_data - Flow filter resource capability limits\n+ * @groups_limit: maximum number of flow filter groups supported by the device\n+ * @classifiers_limit: maximum number of classifiers supported by the device\n+ * @rules_limit: maximum number of rules supported device-wide across all groups\n+ * @rules_per_group_limit: maximum number of rules allowed in a single group\n+ * @last_rule_priority: priority value associated with the lowest-priority rule\n+ * @selectors_per_classifier_limit: maximum selectors allowed in one classifier\n+ */\n+struct virtio_net_ff_cap_data {\n+\t__le32 groups_limit;\n+\t__le32 classifiers_limit;\n+\t__le32 rules_limit;\n+\t__le32 rules_per_group_limit;\n+\t__u8 last_rule_priority;\n+\t__u8 selectors_per_classifier_limit;\n+\t/* private: */\n+\t__u8 reserved[2];\n+};\n+\n+/**\n+ * struct virtio_net_ff_selector - Selector mask descriptor\n+ * @type: selector type, one of VIRTIO_NET_FF_MASK_TYPE_* constants\n+ * @flags: selector flags, see VIRTIO_NET_FF_MASK_F_* constants\n+ * @reserved: must be set to 0 by the driver and ignored by the device\n+ * @length: size in bytes of @mask\n+ * @reserved1: must be set to 0 by the driver and ignored by the device\n+ * @mask: variable-length mask payload for @type, length given by @length\n+ *\n+ * A selector describes a header mask that a classifier can apply. The format\n+ * of @mask depends on @type.\n+ */\n+struct virtio_net_ff_selector {\n+\t__u8 type;\n+\t__u8 flags;\n+\t__u8 reserved[2];\n+\t__u8 length;\n+\t__u8 reserved1[3];\n+\t__u8 mask[] __counted_by(length);\n+};\n+\n+#define VIRTIO_NET_FF_MASK_TYPE_ETH 1\n+#define VIRTIO_NET_FF_MASK_TYPE_IPV4 2\n+#define VIRTIO_NET_FF_MASK_TYPE_IPV6 3\n+#define VIRTIO_NET_FF_MASK_TYPE_TCP 4\n+#define VIRTIO_NET_FF_MASK_TYPE_UDP 5\n+#define VIRTIO_NET_FF_MASK_TYPE_MAX VIRTIO_NET_FF_MASK_TYPE_UDP\n+\n+/**\n+ * struct virtio_net_ff_cap_mask_data - Supported selector mask formats\n+ * @count: number of entries in @selectors\n+ * @reserved: must be set to 0 by the driver and ignored by the device\n+ * @selectors: packed array of struct virtio_net_ff_selector.\n+ */\n+struct virtio_net_ff_cap_mask_data {\n+\t__u8 count;\n+\t__u8 reserved[7];\n+\t__u8 selectors[];\n+};\n+\n+#define VIRTIO_NET_FF_MASK_F_PARTIAL_MASK (1 \u003c\u003c 0)\n+\n+#define VIRTIO_NET_FF_ACTION_DROP 1\n+#define VIRTIO_NET_FF_ACTION_RX_VQ 2\n+#define VIRTIO_NET_FF_ACTION_MAX VIRTIO_NET_FF_ACTION_RX_VQ\n+/**\n+ * struct virtio_net_ff_actions - Supported flow actions\n+ * @count: number of supported actions in @actions\n+ * @reserved: must be set to 0 by the driver and ignored by the device\n+ * @actions: array of action identifiers (VIRTIO_NET_FF_ACTION_*)\n+ */\n+struct virtio_net_ff_actions {\n+\t__u8 count;\n+\t__u8 reserved[7];\n+\t__u8 actions[] __counted_by(count);\n+};\n+\n+/**\n+ * struct virtio_net_resource_obj_ff_group - Flow filter group object\n+ * @group_priority: priority of the group used to order evaluation\n+ *\n+ * This structure is the payload for the VIRTIO_NET_RESOURCE_OBJ_FF_GROUP\n+ * administrative object. Devices use @group_priority to order flow filter\n+ * groups. Multi-byte fields are little-endian.\n+ */\n+struct virtio_net_resource_obj_ff_group {\n+\t__le16 group_priority;\n+};\n+\n+/**\n+ * struct virtio_net_resource_obj_ff_classifier - Flow filter classifier object\n+ * @count: number of selector entries in @selectors\n+ * @reserved: must be set to 0 by the driver and ignored by the device\n+ * @selectors: array of selector descriptors that define match masks\n+ *\n+ * Payload for the VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER administrative object.\n+ * Each selector describes a header mask used to match packets\n+ * (see struct virtio_net_ff_selector). Selectors appear in the order they are\n+ * to be applied.\n+ */\n+struct virtio_net_resource_obj_ff_classifier {\n+\t__u8 count;\n+\t__u8 reserved[7];\n+\t__u8 selectors[];\n+};\n+\n+/**\n+ * struct virtio_net_resource_obj_ff_rule - Flow filter rule object\n+ * @group_id: identifier of the target flow filter group\n+ * @classifier_id: identifier of the classifier referenced by this rule\n+ * @rule_priority: relative priority of this rule within the group\n+ * @key_length: number of bytes in @keys\n+ * @action: action to perform, one of VIRTIO_NET_FF_ACTION_*\n+ * @reserved: must be set to 0 by the driver and ignored by the device\n+ * @vq_index: RX virtqueue index for VIRTIO_NET_FF_ACTION_RX_VQ, 0 otherwise\n+ * @reserved1: must be set to 0 by the driver and ignored by the device\n+ * @keys: concatenated key bytes matching the classifier's selectors order\n+ *\n+ * Payload for the VIRTIO_NET_RESOURCE_OBJ_FF_RULE administrative object.\n+ * @group_id and @classifier_id refer to previously created objects of types\n+ * VIRTIO_NET_RESOURCE_OBJ_FF_GROUP and VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER\n+ * respectively. The key bytes are compared against packet headers using the\n+ * masks provided by the classifier's selectors. Multi-byte fields are\n+ * little-endian.\n+ */\n+struct virtio_net_resource_obj_ff_rule {\n+\t__le32 group_id;\n+\t__le32 classifier_id;\n+\t__u8 rule_priority;\n+\t__u8 key_length; /* length of key in bytes */\n+\t__u8 action;\n+\t__u8 reserved;\n+\t__le16 vq_index;\n+\t__u8 reserved1[2];\n+\t__u8 keys[];\n+};\n+\n+#endif\ndiff --git a/include/uapi/linux/virtio_pci.h b/include/uapi/linux/virtio_pci.h\nindex e732e3456e275..96d097d3757e4 100644\n--- a/include/uapi/linux/virtio_pci.h\n+++ b/include/uapi/linux/virtio_pci.h\n@@ -315,15 +315,17 @@ struct virtio_admin_cmd_notify_info_result {\n \n #define VIRTIO_DEV_PARTS_CAP 0x0000\n \n+#define VIRTIO_ADMIN_MAX_CAP 0x0fff\n+\n struct virtio_dev_parts_cap {\n \t__u8 get_parts_resource_objects_limit;\n \t__u8 set_parts_resource_objects_limit;\n };\n \n-#define MAX_CAP_ID __KERNEL_DIV_ROUND_UP(VIRTIO_DEV_PARTS_CAP + 1, 64)\n+#define VIRTIO_ADMIN_CAP_ID_ARRAY_SIZE __KERNEL_DIV_ROUND_UP(VIRTIO_ADMIN_MAX_CAP + 1, 64)\n \n struct virtio_admin_cmd_query_cap_id_result {\n-\t__le64 supported_caps[MAX_CAP_ID];\n+\t__le64 supported_caps[VIRTIO_ADMIN_CAP_ID_ARRAY_SIZE];\n };\n \n struct virtio_admin_cmd_cap_get_data {\n"
} |
| 2/1 | 2026/08/16 12:47 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"VIRTIO_NET",
"VIRTIO_PCI"
],
"FocusSymbols": [
"virtnet_set_rxnfc",
"virtnet_get_rxnfc",
"virtio_admin_cap_id_list_query",
"virtio_admin_cap_get",
"virtio_admin_cap_set",
"virtio_admin_obj_create",
"virtio_admin_obj_destroy",
"vp_modern_avq_work"
],
"Reasoning": "The patch introduces flow filter support (ethtool rxnfc) to the virtio-net driver, utilizing the new virtio admin command interface. This is a significant functional addition to a core virtualized network driver that is highly reachable in virtualized environments like QEMU/GCE. The changes include new ethtool callbacks and a new virtio admin command API, which should be fuzzed to ensure robustness.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 909d8d1fc9466a0a8af9348a0aec89b0e820e9e7
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Aug 16 12:47:41 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c
index aecfba7deeb03..7247f6cbdb32d 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c
@@ -53,11 +53,6 @@ struct mlx5e_ethtool_steering {
static int flow_type_to_traffic_type(u32 flow_type);
-static u32 flow_type_mask(u32 flow_type)
-{
- return flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS);
-}
-
struct mlx5e_ethtool_rule {
struct list_head list;
struct ethtool_rx_flow_spec flow_spec;
@@ -91,7 +86,7 @@ static struct mlx5e_ethtool_table *get_flow_table(struct mlx5e_priv *priv,
int table_size;
int prio;
- switch (flow_type_mask(fs->flow_type)) {
+ switch (ethtool_flow_type_mask(fs->flow_type)) {
case TCP_V4_FLOW:
case UDP_V4_FLOW:
case TCP_V6_FLOW:
@@ -350,7 +345,7 @@ static int set_flow_attrs(u32 *match_c, u32 *match_v,
outer_headers);
void *outer_headers_v = MLX5_ADDR_OF(fte_match_param, match_v,
outer_headers);
- u32 flow_type = flow_type_mask(fs->flow_type);
+ u32 flow_type = ethtool_flow_type_mask(fs->flow_type);
switch (flow_type) {
case TCP_V4_FLOW:
@@ -435,7 +430,7 @@ static int flow_get_tirn(struct mlx5e_priv *priv,
if (!rss)
return -ENOENT;
- flow_type = flow_type_mask(fs->flow_type);
+ flow_type = ethtool_flow_type_mask(fs->flow_type);
tt = flow_type_to_traffic_type(flow_type);
if (tt < 0)
return -EINVAL;
@@ -673,7 +668,7 @@ static int validate_flow(struct mlx5e_priv *priv,
if (fs->ring_cookie >= priv->channels.params.num_channels)
return -EINVAL;
- switch (flow_type_mask(fs->flow_type)) {
+ switch (ethtool_flow_type_mask(fs->flow_type)) {
case ETHER_FLOW:
num_tuples += validate_ethter(fs);
break;
@@ -906,7 +901,7 @@ int mlx5e_ethtool_set_rxfh_fields(struct mlx5e_priv *priv,
rss_idx = nfc->rss_context;
- flow_type = flow_type_mask(nfc->flow_type);
+ flow_type = ethtool_flow_type_mask(nfc->flow_type);
tt = flow_type_to_traffic_type(flow_type);
if (tt < 0)
return tt;
@@ -951,7 +946,7 @@ int mlx5e_ethtool_get_rxfh_fields(struct mlx5e_priv *priv,
rss_idx = nfc->rss_context;
- flow_type = flow_type_mask(nfc->flow_type);
+ flow_type = ethtool_flow_type_mask(nfc->flow_type);
tt = flow_type_to_traffic_type(flow_type);
if (tt < 0)
return tt;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c
index 01ddc3def9ac0..83eaceddc4375 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c
@@ -233,11 +233,6 @@ static int mlx5i_get_link_ksettings(struct net_device *netdev,
return 0;
}
-static u32 mlx5i_flow_type_mask(u32 flow_type)
-{
- return flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS);
-}
-
static int mlx5i_set_rxfh_fields(struct net_device *dev,
const struct ethtool_rxfh_fields *cmd,
struct netlink_ext_ack *extack)
@@ -260,7 +255,7 @@ static int mlx5i_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
struct mlx5e_priv *priv = mlx5i_epriv(dev);
struct ethtool_rx_flow_spec *fs = &cmd->fs;
- if (mlx5i_flow_type_mask(fs->flow_type) == ETHER_FLOW)
+ if (ethtool_flow_type_mask(fs->flow_type) == ETHER_FLOW)
return -EINVAL;
return mlx5e_ethtool_set_rxnfc(priv, cmd);
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index 3e2a5876c6c8c..87365ca05c259 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -27,6 +27,15 @@
#include <net/netdev_queues.h>
#include <net/xdp_sock_drv.h>
#include <net/page_pool/helpers.h>
+#include <linux/virtio_admin.h>
+#include <net/ipv6.h>
+#include <net/ip.h>
+#include <uapi/linux/virtio_net_ff.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/xarray.h>
+#include <linux/refcount.h>
+#include <linux/unaligned.h>
static int napi_weight = NAPI_POLL_WEIGHT;
module_param(napi_weight, int, 0444);
@@ -282,6 +291,24 @@ static const struct virtnet_stat_desc virtnet_stats_tx_speed_desc_qstat[] = {
VIRTNET_STATS_DESC_TX_QSTAT(speed, ratelimit_packets, hw_drop_ratelimits),
};
+struct virtnet_ethtool_ff {
+ struct xarray rules;
+ int num_rules;
+};
+
+#define VIRTNET_FF_ETHTOOL_GROUP_PRIORITY 0
+#define VIRTNET_FF_MAX_GROUPS 1
+
+struct virtnet_ff {
+ struct virtio_device *vdev;
+ bool ff_supported;
+ struct virtio_net_ff_cap_data *ff_caps;
+ struct virtio_net_ff_cap_mask_data *ff_mask;
+ struct virtio_net_ff_actions *ff_actions;
+ struct xarray classifiers;
+ struct virtnet_ethtool_ff ethtool;
+};
+
#define VIRTNET_Q_TYPE_RX 0
#define VIRTNET_Q_TYPE_TX 1
#define VIRTNET_Q_TYPE_CQ 2
@@ -474,6 +501,8 @@ struct virtnet_info {
struct virtio_net_rss_config_hdr *rss_hdr;
+ struct virtnet_ff ff;
+
/* Must be last as it ends in a flexible-array member. */
TRAILING_OVERLAP(struct virtio_net_rss_config_trailer, rss_trailer, hash_key_data,
u8 rss_hash_key_data[NETDEV_RSS_KEY_LEN];
@@ -519,6 +548,7 @@ static struct sk_buff *virtnet_skb_append_frag(struct receive_queue *rq,
static void virtnet_xsk_completed(struct send_queue *sq, int num);
static void free_unused_bufs(struct virtnet_info *vi);
static void virtnet_del_vqs(struct virtnet_info *vi);
+static void remove_vq_common(struct virtnet_info *vi);
enum virtnet_xmit_type {
VIRTNET_XMIT_TYPE_SKB,
@@ -5587,34 +5617,6 @@ static u32 virtnet_get_rx_ring_count(struct net_device *dev)
return vi->curr_queue_pairs;
}
-static const struct ethtool_ops virtnet_ethtool_ops = {
- .supported_coalesce_params = ETHTOOL_COALESCE_MAX_FRAMES |
- ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_USE_ADAPTIVE_RX,
- .get_drvinfo = virtnet_get_drvinfo,
- .get_link = ethtool_op_get_link,
- .get_ringparam = virtnet_get_ringparam,
- .set_ringparam = virtnet_set_ringparam,
- .get_strings = virtnet_get_strings,
- .get_sset_count = virtnet_get_sset_count,
- .get_ethtool_stats = virtnet_get_ethtool_stats,
- .set_channels = virtnet_set_channels,
- .get_channels = virtnet_get_channels,
- .get_ts_info = ethtool_op_get_ts_info,
- .get_link_ksettings = virtnet_get_link_ksettings,
- .set_link_ksettings = virtnet_set_link_ksettings,
- .set_coalesce = virtnet_set_coalesce,
- .get_coalesce = virtnet_get_coalesce,
- .set_per_queue_coalesce = virtnet_set_per_queue_coalesce,
- .get_per_queue_coalesce = virtnet_get_per_queue_coalesce,
- .get_rxfh_key_size = virtnet_get_rxfh_key_size,
- .get_rxfh_indir_size = virtnet_get_rxfh_indir_size,
- .get_rxfh = virtnet_get_rxfh,
- .set_rxfh = virtnet_set_rxfh,
- .get_rxfh_fields = virtnet_get_hashflow,
- .set_rxfh_fields = virtnet_set_hashflow,
- .get_rx_ring_count = virtnet_get_rx_ring_count,
-};
-
static void virtnet_get_queue_stats_rx(struct net_device *dev, int i,
struct netdev_queue_stats_rx *stats)
{
@@ -5710,213 +5712,1423 @@ static const struct netdev_stat_ops virtnet_stat_ops = {
.get_base_stats = virtnet_get_base_stats,
};
-static void virtnet_freeze_down(struct virtio_device *vdev)
-{
- struct virtnet_info *vi = vdev->priv;
+struct virtnet_ethtool_rule {
+ struct ethtool_rx_flow_spec flow_spec;
+ u32 classifier_id;
+};
- /* Make sure no work handler is accessing the device */
- flush_work(&vi->config_work);
- disable_rx_mode_work(vi);
- flush_work(&vi->rx_mode_work);
+/* The classifier struct must be the last field in this struct */
+struct virtnet_classifier {
+ size_t size;
+ refcount_t refcount;
+ u32 id;
+ struct virtio_net_resource_obj_ff_classifier obj;
+};
- if (netif_running(vi->dev)) {
- rtnl_lock();
- virtnet_close(vi->dev);
- rtnl_unlock();
+static_assert(sizeof(struct virtnet_classifier) ==
+ ALIGN(offsetofend(struct virtnet_classifier, obj),
+ __alignof__(struct virtnet_classifier)),
+ "virtnet_classifier: classifier must be the last member");
+
+static bool check_mask_vs_cap(const void *m, const void *c,
+ u16 len, bool partial)
+{
+ const u8 *mask = m;
+ const u8 *cap = c;
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (partial && ((mask[i] & cap[i]) != mask[i]))
+ return false;
+ if (!partial && mask[i] != cap[i])
+ return false;
}
- netif_tx_lock_bh(vi->dev);
- netif_device_detach(vi->dev);
- netif_tx_unlock_bh(vi->dev);
+ return true;
}
-static int init_vqs(struct virtnet_info *vi);
+static
+struct virtio_net_ff_selector *get_selector_cap(const struct virtnet_ff *ff,
+ u8 selector_type)
+{
+ struct virtio_net_ff_selector *sel;
+ void *buf;
+ int i;
-static int virtnet_restore_up(struct virtio_device *vdev)
+ buf = &ff->ff_mask->selectors;
+ sel = buf;
+
+ for (i = 0; i < ff->ff_mask->count; i++) {
+ if (sel->type == selector_type)
+ return sel;
+
+ buf += sizeof(struct virtio_net_ff_selector) + sel->length;
+ sel = buf;
+ }
+
+ return NULL;
+}
+
+static bool validate_eth_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel,
+ const struct virtio_net_ff_selector *sel_cap)
{
- struct virtnet_info *vi = vdev->priv;
- int err;
+ bool partial_mask = !!(sel_cap->flags & VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);
+ struct ethhdr *cap, *mask;
+ struct ethhdr zeros = {};
- err = init_vqs(vi);
- if (err)
- return err;
+ cap = (struct ethhdr *)&sel_cap->mask;
+ mask = (struct ethhdr *)&sel->mask;
- err = virtnet_create_page_pools(vi);
- if (err)
- goto err_del_vqs;
+ if (memcmp(&zeros.h_dest, mask->h_dest, sizeof(zeros.h_dest)) &&
+ !check_mask_vs_cap(mask->h_dest, cap->h_dest,
+ sizeof(mask->h_dest), partial_mask))
+ return false;
- virtio_device_ready(vdev);
+ if (memcmp(&zeros.h_source, mask->h_source, sizeof(zeros.h_source)) &&
+ !check_mask_vs_cap(mask->h_source, cap->h_source,
+ sizeof(mask->h_source), partial_mask))
+ return false;
- enable_rx_mode_work(vi);
+ if (mask->h_proto &&
+ !check_mask_vs_cap(&mask->h_proto, &cap->h_proto,
+ sizeof(__be16), partial_mask))
+ return false;
- if (netif_running(vi->dev)) {
- rtnl_lock();
- err = virtnet_open(vi->dev);
- rtnl_unlock();
- if (err)
- goto err_destroy_pools;
- }
+ return true;
+}
- netif_tx_lock_bh(vi->dev);
- netif_device_attach(vi->dev);
- netif_tx_unlock_bh(vi->dev);
- return 0;
+static bool validate_ip4_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel,
+ const struct virtio_net_ff_selector *sel_cap)
+{
+ bool partial_mask = !!(sel_cap->flags & VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);
+ struct iphdr *cap, *mask;
-err_destroy_pools:
- virtio_reset_device(vdev);
- free_unused_bufs(vi);
- virtnet_destroy_page_pools(vi);
- virtnet_del_vqs(vi);
- return err;
+ cap = (struct iphdr *)&sel_cap->mask;
+ mask = (struct iphdr *)&sel->mask;
-err_del_vqs:
- virtio_reset_device(vdev);
- virtnet_del_vqs(vi);
- return err;
+ if (get_unaligned(&mask->saddr) &&
+ !check_mask_vs_cap(&mask->saddr, &cap->saddr,
+ sizeof(__be32), partial_mask))
+ return false;
+
+ if (get_unaligned(&mask->daddr) &&
+ !check_mask_vs_cap(&mask->daddr, &cap->daddr,
+ sizeof(__be32), partial_mask))
+ return false;
+
+ if (mask->protocol &&
+ !check_mask_vs_cap(&mask->protocol, &cap->protocol,
+ sizeof(u8), partial_mask))
+ return false;
+
+ if (mask->tos &&
+ !check_mask_vs_cap(&mask->tos, &cap->tos,
+ sizeof(u8), partial_mask))
+ return false;
+
+ return true;
}
-static int virtnet_set_guest_offloads(struct virtnet_info *vi, u64 offloads)
+static bool validate_ip6_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel,
+ const struct virtio_net_ff_selector *sel_cap)
{
- __virtio64 *_offloads __free(kfree) = NULL;
- struct scatterlist sg;
+ bool partial_mask = !!(sel_cap->flags & VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);
+ struct in6_addr tmp;
+ struct ipv6hdr *cap, *mask;
- _offloads = kzalloc_obj(*_offloads);
- if (!_offloads)
- return -ENOMEM;
+ cap = (struct ipv6hdr *)&sel_cap->mask;
+ mask = (struct ipv6hdr *)&sel->mask;
- *_offloads = cpu_to_virtio64(vi->vdev, offloads);
+ /* mask->saddr/daddr may be unaligned; copy to aligned tmp for
+ * ipv6_addr_any().
+ */
+ memcpy(&tmp, &mask->saddr, sizeof(tmp));
+ if (!ipv6_addr_any(&tmp) &&
+ !check_mask_vs_cap(&mask->saddr, &cap->saddr,
+ sizeof(cap->saddr), partial_mask))
+ return false;
- sg_init_one(&sg, _offloads, sizeof(*_offloads));
+ memcpy(&tmp, &mask->daddr, sizeof(tmp));
+ if (!ipv6_addr_any(&tmp) &&
+ !check_mask_vs_cap(&mask->daddr, &cap->daddr,
+ sizeof(cap->daddr), partial_mask))
+ return false;
- if (!virtnet_send_command(vi, VIRTIO_NET_CTRL_GUEST_OFFLOADS,
- VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, &sg)) {
- dev_warn(&vi->dev->dev, "Fail to set guest offload.\n");
- return -EINVAL;
- }
+ if (mask->nexthdr &&
+ !check_mask_vs_cap(&mask->nexthdr, &cap->nexthdr,
+ sizeof(cap->nexthdr), partial_mask))
+ return false;
- return 0;
+ return true;
}
-static int virtnet_clear_guest_offloads(struct virtnet_info *vi)
+static bool validate_tcp_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel,
+ const struct virtio_net_ff_selector *sel_cap)
{
- u64 offloads = 0;
+ bool partial_mask = !!(sel_cap->flags & VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);
+ struct tcphdr *cap, *mask;
- if (!vi->guest_offloads)
- return 0;
+ cap = (struct tcphdr *)&sel_cap->mask;
+ mask = (struct tcphdr *)&sel->mask;
- return virtnet_set_guest_offloads(vi, offloads);
+ if (get_unaligned(&mask->source) &&
+ !check_mask_vs_cap(&mask->source, &cap->source,
+ sizeof(cap->source), partial_mask))
+ return false;
+
+ if (get_unaligned(&mask->dest) &&
+ !check_mask_vs_cap(&mask->dest, &cap->dest,
+ sizeof(cap->dest), partial_mask))
+ return false;
+
+ return true;
}
-static int virtnet_restore_guest_offloads(struct virtnet_info *vi)
+static bool validate_udp_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel,
+ const struct virtio_net_ff_selector *sel_cap)
{
- u64 offloads = vi->guest_offloads;
+ bool partial_mask = !!(sel_cap->flags & VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);
+ struct udphdr *cap, *mask;
- if (!vi->guest_offloads)
- return 0;
+ cap = (struct udphdr *)&sel_cap->mask;
+ mask = (struct udphdr *)&sel->mask;
- return virtnet_set_guest_offloads(vi, offloads);
+ if (get_unaligned(&mask->source) &&
+ !check_mask_vs_cap(&mask->source, &cap->source,
+ sizeof(cap->source), partial_mask))
+ return false;
+
+ if (get_unaligned(&mask->dest) &&
+ !check_mask_vs_cap(&mask->dest, &cap->dest,
+ sizeof(cap->dest), partial_mask))
+ return false;
+
+ return true;
}
-static int virtnet_rq_bind_xsk_pool(struct virtnet_info *vi, struct receive_queue *rq,
- struct xsk_buff_pool *pool)
+static bool validate_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel)
{
- int err, qindex;
+ struct virtio_net_ff_selector *sel_cap = get_selector_cap(ff, sel->type);
- qindex = rq - vi->rq;
+ if (!sel_cap)
+ return false;
- if (pool) {
- err = xdp_rxq_info_reg(&rq->xsk_rxq_info, vi->dev, qindex, rq->napi.napi_id);
- if (err < 0)
- return err;
+ switch (sel->type) {
+ case VIRTIO_NET_FF_MASK_TYPE_ETH:
+ return validate_eth_mask(ff, sel, sel_cap);
- err = xdp_rxq_info_reg_mem_model(&rq->xsk_rxq_info,
- MEM_TYPE_XSK_BUFF_POOL, NULL);
- if (err < 0)
- goto unreg;
+ case VIRTIO_NET_FF_MASK_TYPE_IPV4:
+ return validate_ip4_mask(ff, sel, sel_cap);
- xsk_pool_set_rxq_info(pool, &rq->xsk_rxq_info);
+ case VIRTIO_NET_FF_MASK_TYPE_IPV6:
+ return validate_ip6_mask(ff, sel, sel_cap);
+
+ case VIRTIO_NET_FF_MASK_TYPE_TCP:
+ return validate_tcp_mask(ff, sel, sel_cap);
+
+ case VIRTIO_NET_FF_MASK_TYPE_UDP:
+ return validate_udp_mask(ff, sel, sel_cap);
}
- virtnet_rx_pause(vi, rq);
+ return false;
+}
- err = virtqueue_reset(rq->vq, virtnet_rq_unmap_free_buf, NULL);
- if (err) {
- netdev_err(vi->dev, "reset rx fail: rx queue index: %d err: %d\n", qindex, err);
+static void set_tcp(struct tcphdr *mask, struct tcphdr *key,
+ __be16 psrc_m, __be16 psrc_k,
+ __be16 pdst_m, __be16 pdst_k)
+{
+ /* mask/key may be unaligned; use memcpy */
+ if (psrc_m) {
+ memcpy(&mask->source, &psrc_m, sizeof(mask->source));
+ memcpy(&key->source, &psrc_k, sizeof(key->source));
+ }
+ if (pdst_m) {
+ memcpy(&mask->dest, &pdst_m, sizeof(mask->dest));
+ memcpy(&key->dest, &pdst_k, sizeof(key->dest));
+ }
+}
- pool = NULL;
+static void set_udp(struct udphdr *mask, struct udphdr *key,
+ __be16 psrc_m, __be16 psrc_k,
+ __be16 pdst_m, __be16 pdst_k)
+{
+ /* mask/key may be unaligned; use memcpy */
+ if (psrc_m) {
+ memcpy(&mask->source, &psrc_m, sizeof(mask->source));
+ memcpy(&key->source, &psrc_k, sizeof(key->source));
+ }
+ if (pdst_m) {
+ memcpy(&mask->dest, &pdst_m, sizeof(mask->dest));
+ memcpy(&key->dest, &pdst_k, sizeof(key->dest));
}
+}
- rq->xsk_pool = pool;
+static void parse_ip4(struct iphdr *mask, struct iphdr *key,
+ const struct ethtool_rx_flow_spec *fs)
+{
+ const struct ethtool_usrip4_spec *l3_mask = &fs->m_u.usr_ip4_spec;
+ const struct ethtool_usrip4_spec *l3_val = &fs->h_u.usr_ip4_spec;
- virtnet_rx_resume(vi, rq, true);
+ if (l3_mask->ip4src) {
+ put_unaligned(l3_mask->ip4src, &mask->saddr);
+ put_unaligned(l3_val->ip4src, &key->saddr);
+ }
- if (pool)
- return 0;
+ if (l3_mask->ip4dst) {
+ put_unaligned(l3_mask->ip4dst, &mask->daddr);
+ put_unaligned(l3_val->ip4dst, &key->daddr);
+ }
-unreg:
- xdp_rxq_info_unreg(&rq->xsk_rxq_info);
- return err;
+ if (l3_mask->tos) {
+ mask->tos = l3_mask->tos;
+ key->tos = l3_val->tos;
+ }
}
-static int virtnet_sq_bind_xsk_pool(struct virtnet_info *vi,
- struct send_queue *sq,
- struct xsk_buff_pool *pool)
+static void parse_ip6(struct ipv6hdr *mask, struct ipv6hdr *key,
+ const struct ethtool_rx_flow_spec *fs)
{
- int err, qindex;
-
- qindex = sq - vi->sq;
+ const struct ethtool_usrip6_spec *l3_mask = &fs->m_u.usr_ip6_spec;
+ const struct ethtool_usrip6_spec *l3_val = &fs->h_u.usr_ip6_spec;
- virtnet_tx_pause(vi, sq);
+ if (!ipv6_addr_any((struct in6_addr *)l3_mask->ip6src)) {
+ memcpy(&mask->saddr, l3_mask->ip6src, sizeof(mask->saddr));
+ memcpy(&key->saddr, l3_val->ip6src, sizeof(key->saddr));
+ }
- err = virtqueue_reset(sq->vq, virtnet_sq_free_unused_buf,
- virtnet_sq_free_unused_buf_done);
- if (err) {
- netdev_err(vi->dev, "reset tx fail: tx queue index: %d err: %d\n", qindex, err);
- pool = NULL;
+ if (!ipv6_addr_any((struct in6_addr *)l3_mask->ip6dst)) {
+ memcpy(&mask->daddr, l3_mask->ip6dst, sizeof(mask->daddr));
+ memcpy(&key->daddr, l3_val->ip6dst, sizeof(key->daddr));
}
+}
- sq->xsk_pool = pool;
+static bool has_ipv4(u32 flow_type)
+{
+ return flow_type == TCP_V4_FLOW ||
+ flow_type == UDP_V4_FLOW ||
+ flow_type == IP_USER_FLOW;
+}
- virtnet_tx_resume(vi, sq);
+static bool has_ipv6(u32 flow_type)
+{
+ return flow_type == TCP_V6_FLOW ||
+ flow_type == UDP_V6_FLOW ||
+ flow_type == IPV6_USER_FLOW;
+}
- return err;
+static bool has_tcp(u32 flow_type)
+{
+ return flow_type == TCP_V4_FLOW || flow_type == TCP_V6_FLOW;
}
-static int virtnet_xsk_pool_enable(struct net_device *dev,
- struct xsk_buff_pool *pool,
- u16 qid)
+static bool has_udp(u32 flow_type)
{
- struct virtnet_info *vi = netdev_priv(dev);
- struct receive_queue *rq;
- struct device *dma_dev;
- struct send_queue *sq;
- dma_addr_t hdr_dma;
- int err, size;
+ return flow_type == UDP_V4_FLOW || flow_type == UDP_V6_FLOW;
+}
- if (vi->hdr_len > xsk_pool_get_headroom(pool))
- return -EINVAL;
+static int setup_classifier(struct virtnet_ff *ff,
+ struct virtnet_classifier **c)
+{
+ struct virtnet_classifier *tmp;
+ unsigned long i;
+ int err;
- /* In big_packets mode, xdp cannot work, so there is no need to
- * initialize xsk of rq.
- */
- if (!vi->rq[qid].page_pool)
- return -ENOENT;
+ xa_for_each(&ff->classifiers, i, tmp) {
+ if ((*c)->size == tmp->size &&
+ !memcmp(&tmp->obj, &(*c)->obj, tmp->size)) {
+ refcount_inc(&tmp->refcount);
+ kfree(*c);
+ *c = tmp;
+ goto out;
+ }
+ }
- if (qid >= vi->curr_queue_pairs)
- return -EINVAL;
+ err = xa_alloc(&ff->classifiers, &(*c)->id, *c,
+ XA_LIMIT(0, le32_to_cpu(ff->ff_caps->classifiers_limit) - 1),
+ GFP_KERNEL);
+ if (err)
+ return err;
- sq = &vi->sq[qid];
- rq = &vi->rq[qid];
+ err = virtio_admin_obj_create(ff->vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER,
+ (*c)->id,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0,
+ &(*c)->obj,
+ (*c)->size);
+ if (err)
+ goto err_xarray;
- /* xsk assumes that tx and rx must have the same dma device. The af-xdp
- * may use one buffer to receive from the rx and reuse this buffer to
- * send by the tx. So the dma dev of sq and rq must be the same one.
- *
- * But vq->dma_dev allows every vq has the respective dma dev. So I
- * check the dma dev of vq and sq is the same dev.
- */
- if (virtqueue_dma_dev(rq->vq) != virtqueue_dma_dev(sq->vq))
- return -EINVAL;
+ refcount_set(&(*c)->refcount, 1);
+out:
+ return 0;
+
+err_xarray:
+ xa_erase(&ff->classifiers, (*c)->id);
+
+ return err;
+}
+
+static void try_destroy_classifier(struct virtnet_ff *ff, u32 classifier_id)
+{
+ struct virtnet_classifier *c;
+
+ c = xa_load(&ff->classifiers, classifier_id);
+ if (c && refcount_dec_and_test(&c->refcount)) {
+ virtio_admin_obj_destroy(ff->vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER,
+ c->id,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0);
+
+ xa_erase(&ff->classifiers, c->id);
+ kfree(c);
+ }
+}
+
+static void destroy_ethtool_rule(struct virtnet_ff *ff,
+ struct virtnet_ethtool_rule *eth_rule)
+{
+ ff->ethtool.num_rules--;
+
+ virtio_admin_obj_destroy(ff->vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_RULE,
+ eth_rule->flow_spec.location,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0);
+
+ xa_erase(&ff->ethtool.rules, eth_rule->flow_spec.location);
+ try_destroy_classifier(ff, eth_rule->classifier_id);
+ kfree(eth_rule);
+}
+
+static int insert_rule(struct virtnet_ff *ff,
+ struct virtnet_ethtool_rule *eth_rule,
+ u32 classifier_id,
+ const u8 *key,
+ u8 key_size)
+{
+ struct ethtool_rx_flow_spec *fs = ð_rule->flow_spec;
+ struct virtio_net_resource_obj_ff_rule *ff_rule;
+ int err;
+
+ ff_rule = kzalloc(sizeof(*ff_rule) + key_size, GFP_KERNEL);
+ if (!ff_rule)
+ return -ENOMEM;
+
+ /* Intentionally leave the priority as 0. All rules have the same
+ * priority.
+ */
+ ff_rule->group_id = cpu_to_le32(VIRTNET_FF_ETHTOOL_GROUP_PRIORITY);
+ ff_rule->classifier_id = cpu_to_le32(classifier_id);
+ ff_rule->key_length = key_size;
+ ff_rule->action = fs->ring_cookie == RX_CLS_FLOW_DISC ?
+ VIRTIO_NET_FF_ACTION_DROP :
+ VIRTIO_NET_FF_ACTION_RX_VQ;
+ ff_rule->vq_index = fs->ring_cookie != RX_CLS_FLOW_DISC ?
+ cpu_to_le16(rxq2vq(fs->ring_cookie)) : 0;
+ memcpy(&ff_rule->keys, key, key_size);
+
+ err = virtio_admin_obj_create(ff->vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_RULE,
+ fs->location,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0,
+ ff_rule,
+ sizeof(*ff_rule) + key_size);
+ if (err)
+ goto err_ff_rule;
+
+ eth_rule->classifier_id = classifier_id;
+ ff->ethtool.num_rules++;
+ kfree(ff_rule);
+ kfree(key);
+
+ return 0;
+
+err_ff_rule:
+ kfree(ff_rule);
+
+ return err;
+}
+
+static bool supported_flow_type(const struct ethtool_rx_flow_spec *fs)
+{
+ switch (fs->flow_type) {
+ case ETHER_FLOW:
+ case IP_USER_FLOW:
+ case IPV6_USER_FLOW:
+ case TCP_V4_FLOW:
+ case TCP_V6_FLOW:
+ case UDP_V4_FLOW:
+ case UDP_V6_FLOW:
+ return true;
+ }
+
+ return false;
+}
+
+static int validate_flow_input(struct virtnet_ff *ff,
+ const struct ethtool_rx_flow_spec *fs,
+ u16 curr_queue_pairs)
+{
+ u8 required_action = fs->ring_cookie == RX_CLS_FLOW_DISC ?
+ VIRTIO_NET_FF_ACTION_DROP :
+ VIRTIO_NET_FF_ACTION_RX_VQ;
+ int i;
+
+ /* Force users to use RX_CLS_LOC_ANY - don't allow specific locations */
+ if (fs->location != RX_CLS_LOC_ANY)
+ return -EOPNOTSUPP;
+
+ if (fs->ring_cookie != RX_CLS_FLOW_DISC &&
+ fs->ring_cookie >= curr_queue_pairs)
+ return -EINVAL;
+
+ for (i = 0; i < ff->ff_actions->count; i++)
+ if (ff->ff_actions->actions[i] == required_action)
+ goto action_ok;
+ return -EOPNOTSUPP;
+
+action_ok:
+ if (fs->flow_type != ethtool_flow_type_mask(fs->flow_type))
+ return -EOPNOTSUPP;
+
+ if (!supported_flow_type(fs))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static void calculate_flow_sizes(struct ethtool_rx_flow_spec *fs,
+ u8 *key_size, size_t *classifier_size,
+ int *num_hdrs)
+{
+ size_t size = sizeof(struct ethhdr);
+
+ *num_hdrs = 1;
+
+ if (fs->flow_type != ETHER_FLOW) {
+ ++(*num_hdrs);
+ if (has_ipv4(fs->flow_type))
+ size += sizeof(struct iphdr);
+ else if (has_ipv6(fs->flow_type))
+ size += sizeof(struct ipv6hdr);
+
+ if (has_tcp(fs->flow_type) || has_udp(fs->flow_type)) {
+ ++(*num_hdrs);
+ size += has_tcp(fs->flow_type) ? sizeof(struct tcphdr) :
+ sizeof(struct udphdr);
+ }
+ }
+
+ BUG_ON(size > 0xff);
+ *key_size = size;
+ /*
+ * The classifier size is the size of the classifier header, a selector
+ * header for each type of header in the match criteria, and each header
+ * providing the mask for matching against.
+ */
+ *classifier_size = *key_size +
+ sizeof(struct virtio_net_resource_obj_ff_classifier) +
+ sizeof(struct virtio_net_ff_selector) * (*num_hdrs);
+}
+
+static void setup_eth_hdr_key_mask(struct virtio_net_ff_selector *selector,
+ u8 *key,
+ const struct ethtool_rx_flow_spec *fs,
+ int num_hdrs)
+{
+ struct ethhdr *eth_m = (struct ethhdr *)&selector->mask;
+ struct ethhdr *eth_k = (struct ethhdr *)key;
+
+ selector->type = VIRTIO_NET_FF_MASK_TYPE_ETH;
+ selector->length = sizeof(struct ethhdr);
+
+ if (num_hdrs > 1) {
+ eth_m->h_proto = cpu_to_be16(0xffff);
+ if (has_ipv4(fs->flow_type))
+ eth_k->h_proto = cpu_to_be16(ETH_P_IP);
+ else
+ eth_k->h_proto = cpu_to_be16(ETH_P_IPV6);
+ } else {
+ memcpy(eth_m, &fs->m_u.ether_spec, sizeof(*eth_m));
+ memcpy(eth_k, &fs->h_u.ether_spec, sizeof(*eth_k));
+ }
+}
+
+static int setup_ip_key_mask(struct virtio_net_ff_selector *selector,
+ u8 *key,
+ const struct ethtool_rx_flow_spec *fs,
+ int num_hdrs)
+{
+ struct ipv6hdr *v6_m = (struct ipv6hdr *)&selector->mask;
+ struct iphdr *v4_m = (struct iphdr *)&selector->mask;
+ struct ipv6hdr *v6_k = (struct ipv6hdr *)key;
+ struct iphdr *v4_k = (struct iphdr *)key;
+
+ if (has_ipv6(fs->flow_type)) {
+ selector->type = VIRTIO_NET_FF_MASK_TYPE_IPV6;
+ selector->length = sizeof(struct ipv6hdr);
+
+ /* exclude tclass, it's not exposed directly in struct ipv6hdr */
+ if (fs->h_u.usr_ip6_spec.tclass ||
+ fs->m_u.usr_ip6_spec.tclass ||
+ (num_hdrs == 2 && (fs->h_u.usr_ip6_spec.l4_4_bytes ||
+ fs->m_u.usr_ip6_spec.l4_4_bytes ||
+ fs->h_u.usr_ip6_spec.l4_proto ||
+ fs->m_u.usr_ip6_spec.l4_proto)))
+ return -EINVAL;
+
+ parse_ip6(v6_m, v6_k, fs);
+
+ if (num_hdrs > 2) {
+ v6_m->nexthdr = 0xff;
+ if (has_tcp(fs->flow_type))
+ v6_k->nexthdr = IPPROTO_TCP;
+ else
+ v6_k->nexthdr = IPPROTO_UDP;
+ }
+ } else {
+ selector->type = VIRTIO_NET_FF_MASK_TYPE_IPV4;
+ selector->length = sizeof(struct iphdr);
+
+ if (num_hdrs == 2 &&
+ (fs->h_u.usr_ip4_spec.l4_4_bytes ||
+ fs->h_u.usr_ip4_spec.ip_ver != ETH_RX_NFC_IP4 ||
+ fs->m_u.usr_ip4_spec.l4_4_bytes ||
+ fs->m_u.usr_ip4_spec.ip_ver ||
+ fs->m_u.usr_ip4_spec.proto))
+ return -EINVAL;
+
+ parse_ip4(v4_m, v4_k, fs);
+
+ if (num_hdrs > 2) {
+ v4_m->protocol = 0xff;
+ if (has_tcp(fs->flow_type))
+ v4_k->protocol = IPPROTO_TCP;
+ else
+ v4_k->protocol = IPPROTO_UDP;
+ }
+ }
+
+ return 0;
+}
+
+static int setup_transport_key_mask(struct virtio_net_ff_selector *selector,
+ u8 *key,
+ struct ethtool_rx_flow_spec *fs)
+{
+ struct tcphdr *tcp_m = (struct tcphdr *)&selector->mask;
+ struct udphdr *udp_m = (struct udphdr *)&selector->mask;
+ const struct ethtool_tcpip6_spec *v6_l4_mask;
+ const struct ethtool_tcpip4_spec *v4_l4_mask;
+ const struct ethtool_tcpip6_spec *v6_l4_key;
+ const struct ethtool_tcpip4_spec *v4_l4_key;
+ struct tcphdr *tcp_k = (struct tcphdr *)key;
+ struct udphdr *udp_k = (struct udphdr *)key;
+
+ if (has_tcp(fs->flow_type)) {
+ selector->type = VIRTIO_NET_FF_MASK_TYPE_TCP;
+ selector->length = sizeof(struct tcphdr);
+
+ if (has_ipv6(fs->flow_type)) {
+ v6_l4_mask = &fs->m_u.tcp_ip6_spec;
+ v6_l4_key = &fs->h_u.tcp_ip6_spec;
+
+ set_tcp(tcp_m, tcp_k, v6_l4_mask->psrc, v6_l4_key->psrc,
+ v6_l4_mask->pdst, v6_l4_key->pdst);
+ } else {
+ v4_l4_mask = &fs->m_u.tcp_ip4_spec;
+ v4_l4_key = &fs->h_u.tcp_ip4_spec;
+
+ set_tcp(tcp_m, tcp_k, v4_l4_mask->psrc, v4_l4_key->psrc,
+ v4_l4_mask->pdst, v4_l4_key->pdst);
+ }
+
+ } else if (has_udp(fs->flow_type)) {
+ selector->type = VIRTIO_NET_FF_MASK_TYPE_UDP;
+ selector->length = sizeof(struct udphdr);
+
+ if (has_ipv6(fs->flow_type)) {
+ v6_l4_mask = &fs->m_u.udp_ip6_spec;
+ v6_l4_key = &fs->h_u.udp_ip6_spec;
+
+ set_udp(udp_m, udp_k, v6_l4_mask->psrc, v6_l4_key->psrc,
+ v6_l4_mask->pdst, v6_l4_key->pdst);
+ } else {
+ v4_l4_mask = &fs->m_u.udp_ip4_spec;
+ v4_l4_key = &fs->h_u.udp_ip4_spec;
+
+ set_udp(udp_m, udp_k, v4_l4_mask->psrc, v4_l4_key->psrc,
+ v4_l4_mask->pdst, v4_l4_key->pdst);
+ }
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int
+validate_classifier_selectors(struct virtnet_ff *ff,
+ struct virtio_net_resource_obj_ff_classifier *classifier,
+ int num_hdrs)
+{
+ struct virtio_net_ff_selector *selector = (void *)classifier->selectors;
+ int i;
+
+ if (num_hdrs > ff->ff_caps->selectors_per_classifier_limit)
+ return -EINVAL;
+
+ for (i = 0; i < num_hdrs; i++) {
+ if (!validate_mask(ff, selector))
+ return -EINVAL;
+
+ selector = (((void *)selector) + sizeof(*selector) +
+ selector->length);
+ }
+
+ return 0;
+}
+
+static
+struct virtio_net_ff_selector *next_selector(struct virtio_net_ff_selector *sel)
+{
+ return (void *)sel + sizeof(struct virtio_net_ff_selector) +
+ sel->length;
+}
+
+static int build_and_insert(struct virtnet_ff *ff,
+ struct virtnet_ethtool_rule *eth_rule)
+{
+ struct virtio_net_resource_obj_ff_classifier *classifier;
+ struct ethtool_rx_flow_spec *fs = ð_rule->flow_spec;
+ struct virtio_net_ff_selector *selector;
+ struct virtnet_classifier *c;
+ size_t classifier_size;
+ size_t key_offset;
+ int num_hdrs;
+ u8 key_size;
+ u8 *key;
+ int err;
+
+ calculate_flow_sizes(fs, &key_size, &classifier_size, &num_hdrs);
+
+ key = kzalloc(key_size, GFP_KERNEL);
+ if (!key)
+ return -ENOMEM;
+
+ /*
+ * virtio_net_ff_obj_ff_classifier is already included in the
+ * classifier_size.
+ */
+ c = kzalloc(classifier_size +
+ sizeof(struct virtnet_classifier) -
+ sizeof(struct virtio_net_resource_obj_ff_classifier),
+ GFP_KERNEL);
+ if (!c) {
+ kfree(key);
+ return -ENOMEM;
+ }
+
+ c->size = classifier_size;
+ classifier = &c->obj;
+ classifier->count = num_hdrs;
+ selector = (void *)&classifier->selectors[0];
+
+ setup_eth_hdr_key_mask(selector, key, fs, num_hdrs);
+
+ if (has_ipv4(fs->flow_type) || has_ipv6(fs->flow_type)) {
+ key_offset = selector->length;
+ selector = next_selector(selector);
+
+ err = setup_ip_key_mask(selector, key + key_offset,
+ fs, num_hdrs);
+ if (err)
+ goto err_classifier;
+
+ if (has_udp(fs->flow_type) || has_tcp(fs->flow_type)) {
+ key_offset += selector->length;
+ selector = next_selector(selector);
+
+ err = setup_transport_key_mask(selector,
+ key + key_offset,
+ fs);
+ if (err)
+ goto err_classifier;
+ }
+ }
+
+ err = validate_classifier_selectors(ff, classifier, num_hdrs);
+ if (err)
+ goto err_classifier;
+
+ err = setup_classifier(ff, &c);
+ if (err)
+ goto err_classifier;
+
+ err = insert_rule(ff, eth_rule, c->id, key, key_size);
+ if (err) {
+ /* try_destroy_classifier will decrement the refcount on the
+ * classifier and free it if needed.
+ */
+ try_destroy_classifier(ff, c->id);
+ goto err_key;
+ }
+
+ return 0;
+
+err_classifier:
+ kfree(c);
+err_key:
+ kfree(key);
+
+ return err;
+}
+
+static int virtnet_ethtool_flow_insert(struct virtnet_ff *ff,
+ struct ethtool_rx_flow_spec *fs,
+ u16 curr_queue_pairs)
+{
+ struct virtnet_ethtool_rule *eth_rule;
+ int err;
+
+ if (!ff->ff_supported)
+ return -EOPNOTSUPP;
+
+ err = validate_flow_input(ff, fs, curr_queue_pairs);
+ if (err)
+ return err;
+
+ eth_rule = kzalloc(sizeof(*eth_rule), GFP_KERNEL);
+ if (!eth_rule)
+ return -ENOMEM;
+
+ err = xa_alloc(&ff->ethtool.rules, &fs->location, eth_rule,
+ XA_LIMIT(0, le32_to_cpu(ff->ff_caps->rules_limit) - 1),
+ GFP_KERNEL);
+ if (err)
+ goto err_rule;
+
+ eth_rule->flow_spec = *fs;
+
+ err = build_and_insert(ff, eth_rule);
+ if (err)
+ goto err_xa;
+
+ return err;
+
+err_xa:
+ xa_erase(&ff->ethtool.rules, eth_rule->flow_spec.location);
+
+err_rule:
+ fs->location = RX_CLS_LOC_ANY;
+ kfree(eth_rule);
+
+ return err;
+}
+
+static int virtnet_ethtool_flow_remove(struct virtnet_ff *ff, int location)
+{
+ struct virtnet_ethtool_rule *eth_rule;
+ int err = 0;
+
+ if (!ff->ff_supported)
+ return -EOPNOTSUPP;
+
+ eth_rule = xa_load(&ff->ethtool.rules, location);
+ if (!eth_rule) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ destroy_ethtool_rule(ff, eth_rule);
+out:
+ return err;
+}
+
+static int virtnet_ethtool_get_flow_count(struct virtnet_ff *ff,
+ struct ethtool_rxnfc *info)
+{
+ if (!ff->ff_supported)
+ return -EOPNOTSUPP;
+
+ info->rule_cnt = ff->ethtool.num_rules;
+ info->data = min(le32_to_cpu(ff->ff_caps->rules_limit),
+ le32_to_cpu(ff->ff_caps->rules_per_group_limit)) |
+ RX_CLS_LOC_SPECIAL;
+
+ return 0;
+}
+
+static int virtnet_ethtool_get_flow(struct virtnet_ff *ff,
+ struct ethtool_rxnfc *info)
+{
+ struct virtnet_ethtool_rule *eth_rule;
+
+ if (!ff->ff_supported)
+ return -EOPNOTSUPP;
+
+ eth_rule = xa_load(&ff->ethtool.rules, info->fs.location);
+ if (!eth_rule)
+ return -ENOENT;
+
+ info->fs = eth_rule->flow_spec;
+
+ return 0;
+}
+
+static int
+virtnet_ethtool_get_all_flows(struct virtnet_ff *ff,
+ struct ethtool_rxnfc *info, u32 *rule_locs)
+{
+ struct virtnet_ethtool_rule *eth_rule;
+ unsigned long i = 0;
+ int idx = 0;
+
+ if (!ff->ff_supported)
+ return -EOPNOTSUPP;
+
+ xa_for_each(&ff->ethtool.rules, i, eth_rule) {
+ if (idx == info->rule_cnt)
+ return -EMSGSIZE;
+ rule_locs[idx++] = i;
+ }
+
+ info->data = le32_to_cpu(ff->ff_caps->rules_limit);
+ info->rule_cnt = idx;
+
+ return 0;
+}
+
+static size_t get_mask_size(u16 type)
+{
+ switch (type) {
+ case VIRTIO_NET_FF_MASK_TYPE_ETH:
+ return sizeof(struct ethhdr);
+ case VIRTIO_NET_FF_MASK_TYPE_IPV4:
+ return sizeof(struct iphdr);
+ case VIRTIO_NET_FF_MASK_TYPE_IPV6:
+ return sizeof(struct ipv6hdr);
+ case VIRTIO_NET_FF_MASK_TYPE_TCP:
+ return sizeof(struct tcphdr);
+ case VIRTIO_NET_FF_MASK_TYPE_UDP:
+ return sizeof(struct udphdr);
+ }
+
+ return 0;
+}
+
+static int virtnet_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info, u32 *rule_locs)
+{
+ struct virtnet_info *vi = netdev_priv(dev);
+ int rc;
+
+ switch (info->cmd) {
+ case ETHTOOL_GRXCLSRLCNT:
+ rc = virtnet_ethtool_get_flow_count(&vi->ff, info);
+ break;
+ case ETHTOOL_GRXCLSRULE:
+ rc = virtnet_ethtool_get_flow(&vi->ff, info);
+ break;
+ case ETHTOOL_GRXCLSRLALL:
+ rc = virtnet_ethtool_get_all_flows(&vi->ff, info, rule_locs);
+ break;
+ default:
+ rc = -EOPNOTSUPP;
+ }
+
+ return rc;
+}
+
+static int virtnet_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info)
+{
+ struct virtnet_info *vi = netdev_priv(dev);
+
+ switch (info->cmd) {
+ case ETHTOOL_SRXCLSRLINS:
+ return virtnet_ethtool_flow_insert(&vi->ff, &info->fs,
+ vi->curr_queue_pairs);
+ case ETHTOOL_SRXCLSRLDEL:
+ return virtnet_ethtool_flow_remove(&vi->ff, info->fs.location);
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static const struct ethtool_ops virtnet_ethtool_ops = {
+ .supported_coalesce_params = ETHTOOL_COALESCE_MAX_FRAMES |
+ ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_USE_ADAPTIVE_RX,
+ .get_drvinfo = virtnet_get_drvinfo,
+ .get_link = ethtool_op_get_link,
+ .get_ringparam = virtnet_get_ringparam,
+ .set_ringparam = virtnet_set_ringparam,
+ .get_strings = virtnet_get_strings,
+ .get_sset_count = virtnet_get_sset_count,
+ .get_ethtool_stats = virtnet_get_ethtool_stats,
+ .set_channels = virtnet_set_channels,
+ .get_channels = virtnet_get_channels,
+ .get_ts_info = ethtool_op_get_ts_info,
+ .get_link_ksettings = virtnet_get_link_ksettings,
+ .set_link_ksettings = virtnet_set_link_ksettings,
+ .set_coalesce = virtnet_set_coalesce,
+ .get_coalesce = virtnet_get_coalesce,
+ .set_per_queue_coalesce = virtnet_set_per_queue_coalesce,
+ .get_per_queue_coalesce = virtnet_get_per_queue_coalesce,
+ .get_rxfh_key_size = virtnet_get_rxfh_key_size,
+ .get_rxfh_indir_size = virtnet_get_rxfh_indir_size,
+ .get_rxfh = virtnet_get_rxfh,
+ .set_rxfh = virtnet_set_rxfh,
+ .get_rxfh_fields = virtnet_get_hashflow,
+ .set_rxfh_fields = virtnet_set_hashflow,
+ .get_rx_ring_count = virtnet_get_rx_ring_count,
+ .get_rxnfc = virtnet_get_rxnfc,
+ .set_rxnfc = virtnet_set_rxnfc,
+};
+
+static int virtnet_ff_init(struct virtnet_ff *ff, struct virtio_device *vdev)
+{
+ size_t ff_mask_size = sizeof(struct virtio_net_ff_cap_mask_data) +
+ sizeof(struct virtio_net_ff_selector) *
+ VIRTIO_NET_FF_MASK_TYPE_MAX;
+ struct virtio_net_resource_obj_ff_group ethtool_group = {};
+ struct virtio_admin_cmd_query_cap_id_result *cap_id_list;
+ struct virtio_net_ff_selector *sel;
+ unsigned long sel_types = 0;
+ size_t real_ff_mask_size;
+ int err;
+ int i;
+
+ if (!vdev->config->admin_cmd_exec)
+ return -EOPNOTSUPP;
+
+ cap_id_list = kzalloc(sizeof(*cap_id_list), GFP_KERNEL);
+ if (!cap_id_list)
+ return -ENOMEM;
+
+ err = virtio_admin_cap_id_list_query(vdev, cap_id_list);
+ if (err)
+ goto err_cap_list;
+
+ if (!(virtio_cap_in_list(cap_id_list, VIRTIO_NET_FF_RESOURCE_CAP) &&
+ virtio_cap_in_list(cap_id_list, VIRTIO_NET_FF_SELECTOR_CAP) &&
+ virtio_cap_in_list(cap_id_list, VIRTIO_NET_FF_ACTION_CAP))) {
+ err = -EOPNOTSUPP;
+ goto err_cap_list;
+ }
+
+ ff->ff_caps = kzalloc(sizeof(*ff->ff_caps), GFP_KERNEL);
+ if (!ff->ff_caps) {
+ err = -ENOMEM;
+ goto err_cap_list;
+ }
+
+ err = virtio_admin_cap_get(vdev,
+ VIRTIO_NET_FF_RESOURCE_CAP,
+ ff->ff_caps,
+ sizeof(*ff->ff_caps));
+
+ if (err)
+ goto err_ff;
+
+ if (!le32_to_cpu(ff->ff_caps->groups_limit) ||
+ !le32_to_cpu(ff->ff_caps->classifiers_limit) ||
+ !le32_to_cpu(ff->ff_caps->rules_limit) ||
+ !le32_to_cpu(ff->ff_caps->rules_per_group_limit) ||
+ !ff->ff_caps->selectors_per_classifier_limit) {
+ err = -EINVAL;
+ goto err_ff;
+ }
+
+ /* VIRTIO_NET_FF_MASK_TYPE start at 1 */
+ for (i = 1; i <= VIRTIO_NET_FF_MASK_TYPE_MAX; i++)
+ ff_mask_size += get_mask_size(i);
+
+ ff->ff_mask = kzalloc(ff_mask_size, GFP_KERNEL);
+ if (!ff->ff_mask) {
+ err = -ENOMEM;
+ goto err_ff;
+ }
+
+ err = virtio_admin_cap_get(vdev,
+ VIRTIO_NET_FF_SELECTOR_CAP,
+ ff->ff_mask,
+ ff_mask_size);
+
+ if (err)
+ goto err_ff_mask;
+
+ ff->ff_mask->count = min_t(u8, ff->ff_mask->count,
+ VIRTIO_NET_FF_MASK_TYPE_MAX);
+
+ ff->ff_actions = kzalloc(sizeof(*ff->ff_actions) +
+ VIRTIO_NET_FF_ACTION_MAX,
+ GFP_KERNEL);
+ if (!ff->ff_actions) {
+ err = -ENOMEM;
+ goto err_ff_mask;
+ }
+
+ err = virtio_admin_cap_get(vdev,
+ VIRTIO_NET_FF_ACTION_CAP,
+ ff->ff_actions,
+ sizeof(*ff->ff_actions) + VIRTIO_NET_FF_ACTION_MAX);
+
+ if (err)
+ goto err_ff_action;
+
+ ff->ff_actions->count = min_t(u8, ff->ff_actions->count,
+ VIRTIO_NET_FF_ACTION_MAX);
+ if (!ff->ff_actions->count)
+ goto err_ff_action;
+
+ if (le32_to_cpu(ff->ff_caps->groups_limit) < VIRTNET_FF_MAX_GROUPS) {
+ err = -ENOSPC;
+ goto err_ff_action;
+ }
+ ff->ff_caps->groups_limit = cpu_to_le32(VIRTNET_FF_MAX_GROUPS);
+
+ err = virtio_admin_cap_set(vdev,
+ VIRTIO_NET_FF_RESOURCE_CAP,
+ ff->ff_caps,
+ sizeof(*ff->ff_caps));
+ if (err)
+ goto err_ff_action;
+
+ real_ff_mask_size = sizeof(struct virtio_net_ff_cap_mask_data);
+ sel = (void *)&ff->ff_mask->selectors;
+
+ for (i = 0; i < ff->ff_mask->count; i++) {
+ /* If the selector type is unknown it may indicate the spec
+ * has been revised to include new types of selectors
+ */
+ if (!sel->type || sel->type > VIRTIO_NET_FF_MASK_TYPE_MAX)
+ break;
+
+ if (sel->length != get_mask_size(sel->type) ||
+ test_and_set_bit(sel->type, &sel_types)) {
+ err = -EPROTO;
+ goto err_ff_action;
+ }
+ real_ff_mask_size += sizeof(*sel) + sel->length;
+ if (real_ff_mask_size > ff_mask_size) {
+ err = -EPROTO;
+ goto err_ff_action;
+ }
+ sel = (void *)sel + sizeof(*sel) + sel->length;
+ }
+ ff->ff_mask->count = i;
+
+ err = virtio_admin_cap_set(vdev,
+ VIRTIO_NET_FF_SELECTOR_CAP,
+ ff->ff_mask,
+ real_ff_mask_size);
+ if (err)
+ goto err_ff_action;
+
+ err = virtio_admin_cap_set(vdev,
+ VIRTIO_NET_FF_ACTION_CAP,
+ ff->ff_actions,
+ sizeof(*ff->ff_actions) + ff->ff_actions->count);
+ if (err)
+ goto err_ff_action;
+
+ ethtool_group.group_priority = cpu_to_le16(VIRTNET_FF_ETHTOOL_GROUP_PRIORITY);
+
+ /* Use priority for the object ID. */
+ err = virtio_admin_obj_create(vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_GROUP,
+ VIRTNET_FF_ETHTOOL_GROUP_PRIORITY,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0,
+ ðtool_group,
+ sizeof(ethtool_group));
+ if (err)
+ goto err_ff_action;
+
+ xa_init_flags(&ff->classifiers, XA_FLAGS_ALLOC);
+ xa_init_flags(&ff->ethtool.rules, XA_FLAGS_ALLOC);
+ ff->vdev = vdev;
+ ff->ff_supported = true;
+
+ kfree(cap_id_list);
+
+ return 0;
+
+err_ff_action:
+ kfree(ff->ff_actions);
+ ff->ff_actions = NULL;
+err_ff_mask:
+ kfree(ff->ff_mask);
+ ff->ff_mask = NULL;
+err_ff:
+ kfree(ff->ff_caps);
+ ff->ff_caps = NULL;
+err_cap_list:
+ kfree(cap_id_list);
+
+ return err;
+}
+
+static void virtnet_ff_cleanup(struct virtnet_ff *ff)
+{
+ struct virtnet_ethtool_rule *eth_rule;
+ unsigned long i;
+
+ if (!ff->ff_supported)
+ return;
+
+ xa_for_each(&ff->ethtool.rules, i, eth_rule)
+ destroy_ethtool_rule(ff, eth_rule);
+
+ xa_destroy(&ff->ethtool.rules);
+ xa_destroy(&ff->classifiers);
+
+ virtio_admin_obj_destroy(ff->vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_GROUP,
+ VIRTNET_FF_ETHTOOL_GROUP_PRIORITY,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0);
+
+ kfree(ff->ff_actions);
+ kfree(ff->ff_mask);
+ kfree(ff->ff_caps);
+ ff->ff_supported = false;
+}
+
+static void virtnet_freeze_down(struct virtio_device *vdev)
+{
+ struct virtnet_info *vi = vdev->priv;
+
+ /* Make sure no work handler is accessing the device */
+ flush_work(&vi->config_work);
+ disable_rx_mode_work(vi);
+ flush_work(&vi->rx_mode_work);
+
+ if (netif_running(vi->dev)) {
+ rtnl_lock();
+ virtnet_close(vi->dev);
+ rtnl_unlock();
+ }
+
+ netif_tx_lock_bh(vi->dev);
+ netif_device_detach(vi->dev);
+ netif_tx_unlock_bh(vi->dev);
+
+ rtnl_lock();
+ virtnet_ff_cleanup(&vi->ff);
+ rtnl_unlock();
+}
+
+static int init_vqs(struct virtnet_info *vi);
+
+static int virtnet_restore_up(struct virtio_device *vdev)
+{
+ struct virtnet_info *vi = vdev->priv;
+ int err;
+
+ err = init_vqs(vi);
+ if (err)
+ return err;
+
+ err = virtnet_create_page_pools(vi);
+ if (err)
+ goto err_del_vqs;
+
+ virtio_device_ready(vdev);
+
+ enable_rx_mode_work(vi);
+
+ if (netif_running(vi->dev)) {
+ rtnl_lock();
+ err = virtnet_open(vi->dev);
+ rtnl_unlock();
+ if (err)
+ goto err_destroy_pools;
+ }
+
+ /*
+ * Initialize flow filters. Not supported is an acceptable and common
+ * return code
+ */
+ rtnl_lock();
+ err = virtnet_ff_init(&vi->ff, vi->vdev);
+ if (err && err != -EOPNOTSUPP) {
+ rtnl_unlock();
+ virtnet_freeze_down(vi->vdev);
+ remove_vq_common(vi);
+ return err;
+ }
+ rtnl_unlock();
+
+ netif_tx_lock_bh(vi->dev);
+ netif_device_attach(vi->dev);
+ netif_tx_unlock_bh(vi->dev);
+
+ return 0;
+
+err_destroy_pools:
+ virtio_reset_device(vdev);
+ free_unused_bufs(vi);
+ virtnet_destroy_page_pools(vi);
+ virtnet_del_vqs(vi);
+ return err;
+
+err_del_vqs:
+ virtio_reset_device(vdev);
+ virtnet_del_vqs(vi);
+ return err;
+}
+
+static int virtnet_set_guest_offloads(struct virtnet_info *vi, u64 offloads)
+{
+ __virtio64 *_offloads __free(kfree) = NULL;
+ struct scatterlist sg;
+
+ _offloads = kzalloc_obj(*_offloads);
+ if (!_offloads)
+ return -ENOMEM;
+
+ *_offloads = cpu_to_virtio64(vi->vdev, offloads);
+
+ sg_init_one(&sg, _offloads, sizeof(*_offloads));
+
+ if (!virtnet_send_command(vi, VIRTIO_NET_CTRL_GUEST_OFFLOADS,
+ VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, &sg)) {
+ dev_warn(&vi->dev->dev, "Fail to set guest offload.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int virtnet_clear_guest_offloads(struct virtnet_info *vi)
+{
+ u64 offloads = 0;
+
+ if (!vi->guest_offloads)
+ return 0;
+
+ return virtnet_set_guest_offloads(vi, offloads);
+}
+
+static int virtnet_restore_guest_offloads(struct virtnet_info *vi)
+{
+ u64 offloads = vi->guest_offloads;
+
+ if (!vi->guest_offloads)
+ return 0;
+
+ return virtnet_set_guest_offloads(vi, offloads);
+}
+
+static int virtnet_rq_bind_xsk_pool(struct virtnet_info *vi, struct receive_queue *rq,
+ struct xsk_buff_pool *pool)
+{
+ int err, qindex;
+
+ qindex = rq - vi->rq;
+
+ if (pool) {
+ err = xdp_rxq_info_reg(&rq->xsk_rxq_info, vi->dev, qindex, rq->napi.napi_id);
+ if (err < 0)
+ return err;
+
+ err = xdp_rxq_info_reg_mem_model(&rq->xsk_rxq_info,
+ MEM_TYPE_XSK_BUFF_POOL, NULL);
+ if (err < 0)
+ goto unreg;
+
+ xsk_pool_set_rxq_info(pool, &rq->xsk_rxq_info);
+ }
+
+ virtnet_rx_pause(vi, rq);
+
+ err = virtqueue_reset(rq->vq, virtnet_rq_unmap_free_buf, NULL);
+ if (err) {
+ netdev_err(vi->dev, "reset rx fail: rx queue index: %d err: %d\n", qindex, err);
+
+ pool = NULL;
+ }
+
+ rq->xsk_pool = pool;
+
+ virtnet_rx_resume(vi, rq, true);
+
+ if (pool)
+ return 0;
+
+unreg:
+ xdp_rxq_info_unreg(&rq->xsk_rxq_info);
+ return err;
+}
+
+static int virtnet_sq_bind_xsk_pool(struct virtnet_info *vi,
+ struct send_queue *sq,
+ struct xsk_buff_pool *pool)
+{
+ int err, qindex;
+
+ qindex = sq - vi->sq;
+
+ virtnet_tx_pause(vi, sq);
+
+ err = virtqueue_reset(sq->vq, virtnet_sq_free_unused_buf,
+ virtnet_sq_free_unused_buf_done);
+ if (err) {
+ netdev_err(vi->dev, "reset tx fail: tx queue index: %d err: %d\n", qindex, err);
+ pool = NULL;
+ }
+
+ sq->xsk_pool = pool;
+
+ virtnet_tx_resume(vi, sq);
+
+ return err;
+}
+
+static int virtnet_xsk_pool_enable(struct net_device *dev,
+ struct xsk_buff_pool *pool,
+ u16 qid)
+{
+ struct virtnet_info *vi = netdev_priv(dev);
+ struct receive_queue *rq;
+ struct device *dma_dev;
+ struct send_queue *sq;
+ dma_addr_t hdr_dma;
+ int err, size;
+
+ if (vi->hdr_len > xsk_pool_get_headroom(pool))
+ return -EINVAL;
+
+ /* In big_packets mode, xdp cannot work, so there is no need to
+ * initialize xsk of rq.
+ */
+ if (!vi->rq[qid].page_pool)
+ return -ENOENT;
+
+ if (qid >= vi->curr_queue_pairs)
+ return -EINVAL;
+
+ sq = &vi->sq[qid];
+ rq = &vi->rq[qid];
+
+ /* xsk assumes that tx and rx must have the same dma device. The af-xdp
+ * may use one buffer to receive from the rx and reuse this buffer to
+ * send by the tx. So the dma dev of sq and rq must be the same one.
+ *
+ * But vq->dma_dev allows every vq has the respective dma dev. So I
+ * check the dma dev of vq and sq is the same dev.
+ */
+ if (virtqueue_dma_dev(rq->vq) != virtqueue_dma_dev(sq->vq))
+ return -EINVAL;
dma_dev = virtqueue_dma_dev(rq->vq);
if (!dma_dev)
@@ -7043,6 +8255,15 @@ static int virtnet_probe(struct virtio_device *vdev)
virtio_device_ready(vdev);
+ /* Initialize flow filters. Not supported is an acceptable and common
+ * return code
+ */
+ err = virtnet_ff_init(&vi->ff, vi->vdev);
+ if (err && err != -EOPNOTSUPP) {
+ rtnl_unlock();
+ goto free_unregister_netdev;
+ }
+
if (vi->has_rss || vi->has_rss_hash_report) {
if (!virtnet_commit_rss_command(vi)) {
dev_warn(&vdev->dev, "RSS disabled because committing failed.\n");
@@ -7125,6 +8346,7 @@ static int virtnet_probe(struct virtio_device *vdev)
free_unregister_netdev:
unregister_netdev(dev);
+ virtnet_ff_cleanup(&vi->ff);
free_failover:
net_failover_destroy(vi->failover);
free_page_pools:
@@ -7175,6 +8397,7 @@ static void virtnet_remove(struct virtio_device *vdev)
virtnet_free_irq_moder(vi);
unregister_netdev(vi->dev);
+ virtnet_ff_cleanup(&vi->ff);
net_failover_destroy(vi->failover);
diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
index eefcfe90d6b8b..2b4a204dde331 100644
--- a/drivers/virtio/Makefile
+++ b/drivers/virtio/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_VIRTIO) += virtio.o virtio_ring.o
+obj-$(CONFIG_VIRTIO) += virtio.o virtio_ring.o virtio_admin_commands.o
obj-$(CONFIG_VIRTIO_ANCHOR) += virtio_anchor.o
obj-$(CONFIG_VIRTIO_PCI_LIB) += virtio_pci_modern_dev.o
obj-$(CONFIG_VIRTIO_PCI_LIB_LEGACY) += virtio_pci_legacy_dev.o
diff --git a/drivers/virtio/virtio_admin_commands.c b/drivers/virtio/virtio_admin_commands.c
new file mode 100644
index 0000000000000..08b41f38baea4
--- /dev/null
+++ b/drivers/virtio/virtio_admin_commands.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_admin.h>
+#include <linux/overflow.h>
+#include <uapi/linux/virtio_pci.h>
+
+int virtio_admin_cap_id_list_query(struct virtio_device *vdev,
+ struct virtio_admin_cmd_query_cap_id_result *data)
+{
+ struct virtio_admin_cmd cmd = {};
+ struct scatterlist result_sg;
+
+ if (!vdev->config->admin_cmd_exec)
+ return -EOPNOTSUPP;
+
+ sg_init_one(&result_sg, data, sizeof(*data));
+ cmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_CAP_ID_LIST_QUERY);
+ cmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SELF);
+ cmd.result_sg = &result_sg;
+
+ return vdev->config->admin_cmd_exec(vdev, &cmd);
+}
+EXPORT_SYMBOL_GPL(virtio_admin_cap_id_list_query);
+
+int virtio_admin_cap_get(struct virtio_device *vdev,
+ u16 id,
+ void *caps,
+ size_t cap_size)
+{
+ struct virtio_admin_cmd_cap_get_data *data;
+ struct virtio_admin_cmd cmd = {};
+ struct scatterlist result_sg;
+ struct scatterlist data_sg;
+ int err;
+
+ if (!vdev->config->admin_cmd_exec)
+ return -EOPNOTSUPP;
+
+ data = kzalloc_obj(*data);
+ if (!data)
+ return -ENOMEM;
+
+ data->id = cpu_to_le16(id);
+ sg_init_one(&data_sg, data, sizeof(*data));
+ sg_init_one(&result_sg, caps, cap_size);
+ cmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_DEVICE_CAP_GET);
+ cmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SELF);
+ cmd.data_sg = &data_sg;
+ cmd.result_sg = &result_sg;
+
+ err = vdev->config->admin_cmd_exec(vdev, &cmd);
+ kfree(data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(virtio_admin_cap_get);
+
+int virtio_admin_cap_set(struct virtio_device *vdev,
+ u16 id,
+ const void *caps,
+ size_t cap_size)
+{
+ struct virtio_admin_cmd_cap_set_data *data;
+ struct virtio_admin_cmd cmd = {};
+ struct scatterlist data_sg;
+ size_t data_size;
+ int err;
+
+ if (!vdev->config->admin_cmd_exec)
+ return -EOPNOTSUPP;
+
+ if (check_add_overflow(sizeof(*data), cap_size, &data_size))
+ return -EOVERFLOW;
+
+ data = kzalloc(data_size, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->id = cpu_to_le16(id);
+ memcpy(data->cap_specific_data, caps, cap_size);
+ sg_init_one(&data_sg, data, data_size);
+ cmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_DRIVER_CAP_SET);
+ cmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SELF);
+ cmd.data_sg = &data_sg;
+ cmd.result_sg = NULL;
+
+ err = vdev->config->admin_cmd_exec(vdev, &cmd);
+ kfree(data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(virtio_admin_cap_set);
+
+int virtio_admin_obj_create(struct virtio_device *vdev,
+ u16 obj_type,
+ u32 obj_id,
+ u16 group_type,
+ u64 group_member_id,
+ const void *obj_specific_data,
+ size_t obj_specific_data_size)
+{
+ size_t data_size = sizeof(struct virtio_admin_cmd_resource_obj_create_data);
+ struct virtio_admin_cmd_resource_obj_create_data *obj_create_data;
+ struct virtio_admin_cmd cmd = {};
+ struct scatterlist data_sg;
+ void *data;
+ int err;
+
+ if (!vdev->config->admin_cmd_exec)
+ return -EOPNOTSUPP;
+
+ if (check_add_overflow(data_size, obj_specific_data_size, &data_size))
+ return -EOVERFLOW;
+
+ data = kzalloc(data_size, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ obj_create_data = data;
+ obj_create_data->hdr.type = cpu_to_le16(obj_type);
+ obj_create_data->hdr.id = cpu_to_le32(obj_id);
+ memcpy(obj_create_data->resource_obj_specific_data, obj_specific_data,
+ obj_specific_data_size);
+ sg_init_one(&data_sg, data, data_size);
+
+ cmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_RESOURCE_OBJ_CREATE);
+ cmd.group_type = cpu_to_le16(group_type);
+ cmd.group_member_id = cpu_to_le64(group_member_id);
+ cmd.data_sg = &data_sg;
+
+ err = vdev->config->admin_cmd_exec(vdev, &cmd);
+ kfree(data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(virtio_admin_obj_create);
+
+void virtio_admin_obj_destroy(struct virtio_device *vdev,
+ u16 obj_type,
+ u32 obj_id,
+ u16 group_type,
+ u64 group_member_id)
+{
+ struct virtio_admin_cmd_resource_obj_cmd_hdr *data;
+ struct virtio_admin_cmd cmd = {};
+ struct scatterlist data_sg;
+ int err;
+
+ if (!vdev->config->admin_cmd_exec)
+ return;
+
+ data = kzalloc_obj(*data);
+ if (WARN_ON(!data))
+ return;
+
+ data->type = cpu_to_le16(obj_type);
+ data->id = cpu_to_le32(obj_id);
+ sg_init_one(&data_sg, data, sizeof(*data));
+ cmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_RESOURCE_OBJ_DESTROY);
+ cmd.group_type = cpu_to_le16(group_type);
+ cmd.group_member_id = cpu_to_le64(group_member_id);
+ cmd.data_sg = &data_sg;
+
+ err = vdev->config->admin_cmd_exec(vdev, &cmd);
+ kfree(data);
+
+ WARN_ON_ONCE(err);
+}
+EXPORT_SYMBOL_GPL(virtio_admin_obj_destroy);
diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c
index 164f480b18a6f..ec0c92c782d6e 100644
--- a/drivers/virtio/virtio_pci_common.c
+++ b/drivers/virtio/virtio_pci_common.c
@@ -256,6 +256,11 @@ static void vp_del_vq(struct virtqueue *vq, struct virtio_pci_vq_info *info)
spin_unlock_irqrestore(&vp_dev->lock, flags);
}
+ if (vp_is_avq(vq->vdev, vq->index)) {
+ cancel_work_sync(&vp_dev->admin_vq.work);
+ vp_dev->admin_vq.info = NULL;
+ }
+
vp_dev->del_vq(info);
kfree(info);
}
diff --git a/drivers/virtio/virtio_pci_common.h b/drivers/virtio/virtio_pci_common.h
index 8cd01de27bafe..8b0178295342f 100644
--- a/drivers/virtio/virtio_pci_common.h
+++ b/drivers/virtio/virtio_pci_common.h
@@ -30,6 +30,7 @@
#include <linux/highmem.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
+#include <linux/workqueue.h>
struct virtio_pci_vq_info {
/* the actual virtqueue */
@@ -46,9 +47,10 @@ struct virtio_pci_admin_vq {
/* Virtqueue info associated with this admin queue. */
struct virtio_pci_vq_info *info;
/* Protects virtqueue access. */
- spinlock_t lock;
+ struct mutex lock;
+ /* Admin command completion work. */
+ struct work_struct work;
u64 supported_cmds;
- u64 supported_caps;
u8 max_dev_parts_objects;
struct ida dev_parts_ida;
/* Name of the admin queue: avq.$vq_index. */
diff --git a/drivers/virtio/virtio_pci_modern.c b/drivers/virtio/virtio_pci_modern.c
index 6d8ae2a6a8caf..3b2515e29728a 100644
--- a/drivers/virtio/virtio_pci_modern.c
+++ b/drivers/virtio/virtio_pci_modern.c
@@ -47,13 +47,20 @@ static int vp_avq_index(struct virtio_device *vdev, u16 *index, u16 *num)
void vp_modern_avq_done(struct virtqueue *vq)
{
struct virtio_pci_device *vp_dev = to_vp_device(vq->vdev);
- struct virtio_pci_admin_vq *admin_vq = &vp_dev->admin_vq;
+
+ schedule_work(&vp_dev->admin_vq.work);
+}
+
+static void vp_modern_avq_work(struct work_struct *work)
+{
unsigned int status_size = sizeof(struct virtio_admin_cmd_status);
+ struct virtio_pci_admin_vq *admin_vq =
+ container_of(work, struct virtio_pci_admin_vq, work);
+ struct virtqueue *vq = admin_vq->info->vq;
struct virtio_admin_cmd *cmd;
- unsigned long flags;
unsigned int len;
- spin_lock_irqsave(&admin_vq->lock, flags);
+ mutex_lock(&admin_vq->lock);
do {
virtqueue_disable_cb(vq);
while ((cmd = virtqueue_get_buf(vq, &len))) {
@@ -71,7 +78,7 @@ void vp_modern_avq_done(struct virtqueue *vq)
complete(&cmd->completion);
}
} while (!virtqueue_enable_cb(vq));
- spin_unlock_irqrestore(&admin_vq->lock, flags);
+ mutex_unlock(&admin_vq->lock);
}
static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,
@@ -82,7 +89,6 @@ static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,
struct virtio_admin_cmd *cmd)
{
struct virtqueue *vq;
- unsigned long flags;
int ret;
vq = admin_vq->info->vq;
@@ -100,11 +106,11 @@ static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,
if (virtqueue_is_broken(vq))
return -EIO;
- spin_lock_irqsave(&admin_vq->lock, flags);
+ mutex_lock(&admin_vq->lock);
ret = virtqueue_add_sgs(vq, sgs, out_num, in_num, cmd, GFP_KERNEL);
if (ret < 0) {
if (ret == -ENOSPC) {
- spin_unlock_irqrestore(&admin_vq->lock, flags);
+ mutex_unlock(&admin_vq->lock);
cpu_relax();
goto again;
}
@@ -112,14 +118,14 @@ static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,
}
if (!virtqueue_kick(vq))
goto unlock_err;
- spin_unlock_irqrestore(&admin_vq->lock, flags);
+ mutex_unlock(&admin_vq->lock);
wait_for_completion(&cmd->completion);
return cmd->ret;
unlock_err:
- spin_unlock_irqrestore(&admin_vq->lock, flags);
+ mutex_unlock(&admin_vq->lock);
return -EIO;
}
@@ -304,10 +310,10 @@ virtio_pci_admin_cmd_dev_parts_objects_enable(struct virtio_device *virtio_dev)
static void virtio_pci_admin_cmd_cap_init(struct virtio_device *virtio_dev)
{
- struct virtio_pci_device *vp_dev = to_vp_device(virtio_dev);
struct virtio_admin_cmd_query_cap_id_result *data;
struct virtio_admin_cmd cmd = {};
struct scatterlist result_sg;
+ u64 caps;
int ret;
data = kzalloc_obj(*data);
@@ -323,12 +329,8 @@ static void virtio_pci_admin_cmd_cap_init(struct virtio_device *virtio_dev)
if (ret)
goto end;
- /* Max number of caps fits into a single u64 */
- BUILD_BUG_ON(sizeof(data->supported_caps) > sizeof(u64));
-
- vp_dev->admin_vq.supported_caps = le64_to_cpu(data->supported_caps[0]);
-
- if (!(vp_dev->admin_vq.supported_caps & (1 << VIRTIO_DEV_PARTS_CAP)))
+ caps = le64_to_cpu(data->supported_caps[0]);
+ if (!(caps & BIT_ULL(VIRTIO_DEV_PARTS_CAP)))
goto end;
virtio_pci_admin_cmd_dev_parts_objects_enable(virtio_dev);
@@ -354,6 +356,8 @@ static void vp_modern_avq_cleanup(struct virtio_device *vdev)
if (!virtio_has_feature(vdev, VIRTIO_F_ADMIN_VQ))
return;
+ cancel_work_sync(&vp_dev->admin_vq.work);
+
vq = vp_dev->admin_vq.info->vq;
if (!vq)
return;
@@ -558,10 +562,12 @@ static void vp_reset(struct virtio_device *vdev)
while (vp_modern_get_status(mdev))
msleep(1);
- vp_modern_avq_cleanup(vdev);
-
- /* Flush pending VQ/configuration callbacks. */
+ /* Flush pending VQ/configuration callbacks before cleanup, so that
+ * vp_modern_avq_done() can no longer schedule admin_vq.work.
+ */
vp_synchronize_vectors(vdev);
+
+ vp_modern_avq_cleanup(vdev);
}
static int vp_active_vq(struct virtqueue *vq, u16 msix_vec)
@@ -1242,6 +1248,7 @@ static const struct virtio_config_ops virtio_pci_config_nodev_ops = {
.get_shm_region = vp_get_shm_region,
.disable_vq_and_reset = vp_modern_disable_vq_and_reset,
.enable_vq_after_reset = vp_modern_enable_vq_after_reset,
+ .admin_cmd_exec = vp_modern_admin_cmd_exec,
};
static const struct virtio_config_ops virtio_pci_config_ops = {
@@ -1262,6 +1269,7 @@ static const struct virtio_config_ops virtio_pci_config_ops = {
.get_shm_region = vp_get_shm_region,
.disable_vq_and_reset = vp_modern_disable_vq_and_reset,
.enable_vq_after_reset = vp_modern_enable_vq_after_reset,
+ .admin_cmd_exec = vp_modern_admin_cmd_exec,
};
/* the PCI probing function */
@@ -1288,8 +1296,9 @@ int virtio_pci_modern_probe(struct virtio_pci_device *vp_dev)
vp_dev->avq_index = vp_avq_index;
vp_dev->isr = mdev->isr;
vp_dev->vdev.id = mdev->id;
+ mutex_init(&vp_dev->admin_vq.lock);
+ INIT_WORK(&vp_dev->admin_vq.work, vp_modern_avq_work);
- spin_lock_init(&vp_dev->admin_vq.lock);
return 0;
}
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 12683b5d125e4..adecb9225ee3c 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -1567,4 +1567,10 @@ struct ethtool_forced_speed_map {
void
ethtool_forced_speed_maps_init(struct ethtool_forced_speed_map *maps, u32 size);
+
+static inline u32 ethtool_flow_type_mask(u32 flow_type)
+{
+ return flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS);
+}
+
#endif /* _LINUX_ETHTOOL_H */
diff --git a/include/linux/virtio_admin.h b/include/linux/virtio_admin.h
new file mode 100644
index 0000000000000..9095a04814956
--- /dev/null
+++ b/include/linux/virtio_admin.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Header file for virtio admin operations
+ */
+
+#ifndef _LINUX_VIRTIO_ADMIN_H
+#define _LINUX_VIRTIO_ADMIN_H
+
+#include <linux/bug.h>
+#include <linux/types.h>
+#include <linux/byteorder/generic.h>
+#include <uapi/linux/virtio_pci.h>
+
+struct virtio_device;
+
+/**
+ * virtio_cap_in_list - Check if a capability is supported in the capability list
+ * @cap_list: Pointer to capability list structure containing supported_caps array
+ * @cap: Capability ID to check
+ *
+ * The cap_list contains a supported_caps array of little-endian 64-bit integers
+ * where each bit represents a capability. Bit 0 of the first element represents
+ * capability ID 0, bit 1 represents capability ID 1, and so on.
+ *
+ * Return: true if capability is supported, false otherwise
+ */
+static inline bool virtio_cap_in_list(
+ const struct virtio_admin_cmd_query_cap_id_result *cap_list, u16 cap)
+{
+ BUILD_BUG_ON(cap > VIRTIO_ADMIN_MAX_CAP);
+ return !!(1 & (le64_to_cpu(cap_list->supported_caps[cap / 64]) >>
+ (cap % 64)));
+}
+
+/**
+ * virtio_admin_cap_id_list_query - Query the list of available capability IDs
+ * @vdev: The virtio device to query
+ * @data: Pointer to result structure (must be zero-initialized and heap allocated)
+ *
+ * This function queries the virtio device for the list of available capability
+ * IDs that can be used with virtio_admin_cap_get() and virtio_admin_cap_set().
+ * The result is stored in the provided data structure.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin
+ * operations or capability queries, or a negative error code on other failures.
+ */
+int virtio_admin_cap_id_list_query(struct virtio_device *vdev,
+ struct virtio_admin_cmd_query_cap_id_result *data);
+
+/**
+ * virtio_admin_cap_get - Get capability data for a specific capability ID
+ * @vdev: The virtio device
+ * @id: Capability ID to retrieve
+ * @caps: Pointer to capability data structure (must be heap allocated)
+ * @cap_size: Size of the capability data structure
+ *
+ * This function retrieves a specific capability from the virtio device.
+ * The capability data is stored in the provided buffer. The caller must
+ * ensure the buffer is large enough to hold the capability data.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin
+ * operations or capability retrieval, or a negative error code on other failures.
+ */
+int virtio_admin_cap_get(struct virtio_device *vdev,
+ u16 id,
+ void *caps,
+ size_t cap_size);
+
+/**
+ * virtio_admin_cap_set - Set capability data for a specific capability ID
+ * @vdev: The virtio device
+ * @id: Capability ID to set
+ * @caps: Pointer to capability data structure (must be heap allocated)
+ * @cap_size: Size of the capability data structure
+ *
+ * This function sets a specific capability on the virtio device.
+ * The capability data is read from the provided buffer and applied
+ * to the device. The device may validate the capability data before
+ * applying it.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin
+ * operations or capability setting, or a negative error code on other failures.
+ */
+int virtio_admin_cap_set(struct virtio_device *vdev,
+ u16 id,
+ const void *caps,
+ size_t cap_size);
+
+/**
+ * virtio_admin_obj_create - Create an object on a virtio device
+ * @vdev: the virtio device
+ * @obj_type: type of object to create
+ * @obj_id: ID for the new object
+ * @group_type: administrative group type for the operation
+ * @group_member_id: member identifier within the administrative group
+ * @obj_specific_data: object-specific data for creation
+ * @obj_specific_data_size: size of the object-specific data in bytes
+ *
+ * Creates a new object on the virtio device with the specified type and ID.
+ * The object may require object-specific data for proper initialization.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin
+ * operations or object creation, or a negative error code on other failures.
+ */
+int virtio_admin_obj_create(struct virtio_device *vdev,
+ u16 obj_type,
+ u32 obj_id,
+ u16 group_type,
+ u64 group_member_id,
+ const void *obj_specific_data,
+ size_t obj_specific_data_size);
+
+/**
+ * virtio_admin_obj_destroy - Destroy an object on a virtio device
+ * @vdev: the virtio device
+ * @obj_type: type of object to destroy
+ * @obj_id: ID of the object to destroy
+ * @group_type: administrative group type for the operation
+ * @group_member_id: member identifier within the administrative group
+ *
+ * Destroys an existing object on the virtio device with the specified type
+ * and ID.
+ */
+void virtio_admin_obj_destroy(struct virtio_device *vdev,
+ u16 obj_type,
+ u32 obj_id,
+ u16 group_type,
+ u64 group_member_id);
+
+#endif /* _LINUX_VIRTIO_ADMIN_H */
diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h
index 69f84ea85d71a..e36a32e0a20c8 100644
--- a/include/linux/virtio_config.h
+++ b/include/linux/virtio_config.h
@@ -108,6 +108,10 @@ struct virtqueue_info {
* Returns 0 on success or error status
* If disable_vq_and_reset is set, then enable_vq_after_reset must also be
* set.
+ * @admin_cmd_exec: Execute an admin VQ command (optional).
+ * vdev: the virtio_device
+ * cmd: the command to execute
+ * Returns 0 on success or error status
*/
struct virtio_config_ops {
void (*get)(struct virtio_device *vdev, unsigned offset,
@@ -137,6 +141,8 @@ struct virtio_config_ops {
struct virtio_shm_region *region, u8 id);
int (*disable_vq_and_reset)(struct virtqueue *vq);
int (*enable_vq_after_reset)(struct virtqueue *vq);
+ int (*admin_cmd_exec)(struct virtio_device *vdev,
+ struct virtio_admin_cmd *cmd);
};
/**
diff --git a/include/uapi/linux/virtio_net_ff.h b/include/uapi/linux/virtio_net_ff.h
new file mode 100644
index 0000000000000..9152021c99baa
--- /dev/null
+++ b/include/uapi/linux/virtio_net_ff.h
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+ *
+ * Header file for virtio_net flow filters
+ */
+#ifndef _LINUX_VIRTIO_NET_FF_H
+#define _LINUX_VIRTIO_NET_FF_H
+
+#include <linux/types.h>
+#include <linux/stddef.h>
+
+#define VIRTIO_NET_FF_RESOURCE_CAP 0x800
+#define VIRTIO_NET_FF_SELECTOR_CAP 0x801
+#define VIRTIO_NET_FF_ACTION_CAP 0x802
+
+#define VIRTIO_NET_RESOURCE_OBJ_FF_GROUP 0x0200
+#define VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER 0x0201
+#define VIRTIO_NET_RESOURCE_OBJ_FF_RULE 0x0202
+
+/**
+ * struct virtio_net_ff_cap_data - Flow filter resource capability limits
+ * @groups_limit: maximum number of flow filter groups supported by the device
+ * @classifiers_limit: maximum number of classifiers supported by the device
+ * @rules_limit: maximum number of rules supported device-wide across all groups
+ * @rules_per_group_limit: maximum number of rules allowed in a single group
+ * @last_rule_priority: priority value associated with the lowest-priority rule
+ * @selectors_per_classifier_limit: maximum selectors allowed in one classifier
+ */
+struct virtio_net_ff_cap_data {
+ __le32 groups_limit;
+ __le32 classifiers_limit;
+ __le32 rules_limit;
+ __le32 rules_per_group_limit;
+ __u8 last_rule_priority;
+ __u8 selectors_per_classifier_limit;
+ /* private: */
+ __u8 reserved[2];
+};
+
+/**
+ * struct virtio_net_ff_selector - Selector mask descriptor
+ * @type: selector type, one of VIRTIO_NET_FF_MASK_TYPE_* constants
+ * @flags: selector flags, see VIRTIO_NET_FF_MASK_F_* constants
+ * @reserved: must be set to 0 by the driver and ignored by the device
+ * @length: size in bytes of @mask
+ * @reserved1: must be set to 0 by the driver and ignored by the device
+ * @mask: variable-length mask payload for @type, length given by @length
+ *
+ * A selector describes a header mask that a classifier can apply. The format
+ * of @mask depends on @type.
+ */
+struct virtio_net_ff_selector {
+ __u8 type;
+ __u8 flags;
+ __u8 reserved[2];
+ __u8 length;
+ __u8 reserved1[3];
+ __u8 mask[] __counted_by(length);
+};
+
+#define VIRTIO_NET_FF_MASK_TYPE_ETH 1
+#define VIRTIO_NET_FF_MASK_TYPE_IPV4 2
+#define VIRTIO_NET_FF_MASK_TYPE_IPV6 3
+#define VIRTIO_NET_FF_MASK_TYPE_TCP 4
+#define VIRTIO_NET_FF_MASK_TYPE_UDP 5
+#define VIRTIO_NET_FF_MASK_TYPE_MAX VIRTIO_NET_FF_MASK_TYPE_UDP
+
+/**
+ * struct virtio_net_ff_cap_mask_data - Supported selector mask formats
+ * @count: number of entries in @selectors
+ * @reserved: must be set to 0 by the driver and ignored by the device
+ * @selectors: packed array of struct virtio_net_ff_selector.
+ */
+struct virtio_net_ff_cap_mask_data {
+ __u8 count;
+ __u8 reserved[7];
+ __u8 selectors[];
+};
+
+#define VIRTIO_NET_FF_MASK_F_PARTIAL_MASK (1 << 0)
+
+#define VIRTIO_NET_FF_ACTION_DROP 1
+#define VIRTIO_NET_FF_ACTION_RX_VQ 2
+#define VIRTIO_NET_FF_ACTION_MAX VIRTIO_NET_FF_ACTION_RX_VQ
+/**
+ * struct virtio_net_ff_actions - Supported flow actions
+ * @count: number of supported actions in @actions
+ * @reserved: must be set to 0 by the driver and ignored by the device
+ * @actions: array of action identifiers (VIRTIO_NET_FF_ACTION_*)
+ */
+struct virtio_net_ff_actions {
+ __u8 count;
+ __u8 reserved[7];
+ __u8 actions[] __counted_by(count);
+};
+
+/**
+ * struct virtio_net_resource_obj_ff_group - Flow filter group object
+ * @group_priority: priority of the group used to order evaluation
+ *
+ * This structure is the payload for the VIRTIO_NET_RESOURCE_OBJ_FF_GROUP
+ * administrative object. Devices use @group_priority to order flow filter
+ * groups. Multi-byte fields are little-endian.
+ */
+struct virtio_net_resource_obj_ff_group {
+ __le16 group_priority;
+};
+
+/**
+ * struct virtio_net_resource_obj_ff_classifier - Flow filter classifier object
+ * @count: number of selector entries in @selectors
+ * @reserved: must be set to 0 by the driver and ignored by the device
+ * @selectors: array of selector descriptors that define match masks
+ *
+ * Payload for the VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER administrative object.
+ * Each selector describes a header mask used to match packets
+ * (see struct virtio_net_ff_selector). Selectors appear in the order they are
+ * to be applied.
+ */
+struct virtio_net_resource_obj_ff_classifier {
+ __u8 count;
+ __u8 reserved[7];
+ __u8 selectors[];
+};
+
+/**
+ * struct virtio_net_resource_obj_ff_rule - Flow filter rule object
+ * @group_id: identifier of the target flow filter group
+ * @classifier_id: identifier of the classifier referenced by this rule
+ * @rule_priority: relative priority of this rule within the group
+ * @key_length: number of bytes in @keys
+ * @action: action to perform, one of VIRTIO_NET_FF_ACTION_*
+ * @reserved: must be set to 0 by the driver and ignored by the device
+ * @vq_index: RX virtqueue index for VIRTIO_NET_FF_ACTION_RX_VQ, 0 otherwise
+ * @reserved1: must be set to 0 by the driver and ignored by the device
+ * @keys: concatenated key bytes matching the classifier's selectors order
+ *
+ * Payload for the VIRTIO_NET_RESOURCE_OBJ_FF_RULE administrative object.
+ * @group_id and @classifier_id refer to previously created objects of types
+ * VIRTIO_NET_RESOURCE_OBJ_FF_GROUP and VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER
+ * respectively. The key bytes are compared against packet headers using the
+ * masks provided by the classifier's selectors. Multi-byte fields are
+ * little-endian.
+ */
+struct virtio_net_resource_obj_ff_rule {
+ __le32 group_id;
+ __le32 classifier_id;
+ __u8 rule_priority;
+ __u8 key_length; /* length of key in bytes */
+ __u8 action;
+ __u8 reserved;
+ __le16 vq_index;
+ __u8 reserved1[2];
+ __u8 keys[];
+};
+
+#endif
diff --git a/include/uapi/linux/virtio_pci.h b/include/uapi/linux/virtio_pci.h
index e732e3456e275..96d097d3757e4 100644
--- a/include/uapi/linux/virtio_pci.h
+++ b/include/uapi/linux/virtio_pci.h
@@ -315,15 +315,17 @@ struct virtio_admin_cmd_notify_info_result {
#define VIRTIO_DEV_PARTS_CAP 0x0000
+#define VIRTIO_ADMIN_MAX_CAP 0x0fff
+
struct virtio_dev_parts_cap {
__u8 get_parts_resource_objects_limit;
__u8 set_parts_resource_objects_limit;
};
-#define MAX_CAP_ID __KERNEL_DIV_ROUND_UP(VIRTIO_DEV_PARTS_CAP + 1, 64)
+#define VIRTIO_ADMIN_CAP_ID_ARRAY_SIZE __KERNEL_DIV_ROUND_UP(VIRTIO_ADMIN_MAX_CAP + 1, 64)
struct virtio_admin_cmd_query_cap_id_result {
- __le64 supported_caps[MAX_CAP_ID];
+ __le64 supported_caps[VIRTIO_ADMIN_CAP_ID_ARRAY_SIZE];
};
struct virtio_admin_cmd_cap_get_data {
|
| 3/2 | 2026/08/16 12:47 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33228 output: 22 thoughts: 1086 Thoughts: **Fuzzing Target Identification: Virtio-Net Flow Filter Implementation** Okay, so this patch introduces flow filter support (ethtool rxnfc) to the virtio-net driver. This is a big deal, especially given virtio-net's prevalence in virtualized environments. It looks like we have a new feature that definitely warrants fuzzing. We have to identify the relevant code and prioritize it. First, the patch touches a few files. It refactors a bit in the Mellanox code, but that's not our focus here. The real meat is in `drivers/net/virtio_net.c`, `drivers/virtio/virtio_admin_commands.c`, `drivers/virtio/virtio_pci_modern.c`, and a couple of supporting files. Now, let's break down the functions in `virtio_net.c`. We've got `virtnet_ethtool_flow_insert`, `virtnet_ethtool_flow_remove`, and the related get functions. These seem like core flow filter manipulation functions. Also, the rxnfc functions, `virtnet_get_rxnfc` and `virtnet_set_rxnfc`, stand out as the entry points for the ethtool interface. Further we have the flow filter initialization and cleanup functions `virtnet_ff_init` and `virtnet_ff_cleanup`. Moving on to `virtio_admin_commands.c`, there are several functions which deal with the admin capabilities: `virtio_admin_cap_id_list_query`, `virtio_admin_cap_get`, `virtio_admin_cap_set`, and object creation/destruction functions such as `virtio_admin_obj_create` and `virtio_admin_obj_destroy`. Then, `virtio_pci_modern.c` has these two functions associated with admin queues, `vp_modern_avq_done` and `vp_modern_avq_work` and `virtqueue_exec_admin_cmd`. Okay, the ethtool entry points, `virtnet_set_rxnfc` and `virtnet_get_rxnfc` look promising. These are the ones we want to pay special attention to since they will be the public facing APIs we will trigger to drive fuzzing. Now for the required configurations, the obvious ones are `CONFIG_VIRTIO_NET` and `CONFIG_VIRTIO_PCI`. Also, this patch adds `virtio_admin_commands.o` to the `obj-$(CONFIG_VIRTIO)` build. Let me verify that the symbols are exported and accessible: `virtnet_set_rxnfc`, `virtnet_get_rxnfc`, `virtio_admin_cap_id_list_query`, `virtio_admin_cap_get`, `virtio_admin_cap_set`, `virtio_admin_obj_create`, `virtio_admin_obj_destroy`, and `vp_modern_avq_work`. Yes. Wait, `virtnet_set_rxnfc` is static. That's unusual for an API entry point, but it's an ethtool operation, which is typically called as a callback. And since `virtnet_set_rxnfc` is a callback, it is the entry point so we will use it. Also `virtio_admin_cap_get` is exported. So, the focus symbols are: - `virtnet_set_rxnfc` - `virtnet_get_rxnfc` - `virtio_admin_cap_id_list_query` - `virtio_admin_cap_get` - `virtio_admin_cap_set` - `virtio_admin_obj_create` - `virtio_admin_obj_destroy` - `vp_modern_avq_work` We'll keep an eye on `virtnet_ethtool_flow_insert` and `virtnet_ethtool_flow_remove`. They are static helper functions and may be indirectly fuzzed by the entry point functions. We'll verify everything using the tools. We also need to remember about the configurations. |
| 4/2 | 2026/08/16 12:47 | tool | grepper |
0mArgs:{
"Expression": "virtnet_set_rxnfc"
}Results: {
"Output": "drivers/net/virtio_net.c=6629=static int virtnet_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info, u32 *rule_locs)\n--\ndrivers/net/virtio_net.c-6650-\ndrivers/net/virtio_net.c:6651:static int virtnet_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info)\ndrivers/net/virtio_net.c-6652-{\n--\ndrivers/net/virtio_net.c=6666=static const struct ethtool_ops virtnet_ethtool_ops = {\n--\ndrivers/net/virtio_net.c-6692-\t.get_rxnfc = virtnet_get_rxnfc,\ndrivers/net/virtio_net.c:6693:\t.set_rxnfc = virtnet_set_rxnfc,\ndrivers/net/virtio_net.c-6694-};\n"
} |
| 5/2 | 2026/08/16 12:47 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 35538 output: 22 thoughts: 180 Thoughts: **My Assessment of the Virtio-Net Patch** Okay, so the functions are definitely there and accessible, which is the first good sign. This patch is adding flow filter support for virtio-net, and that's a *functional* change – a real benefit that userspace can interact with directly using ethtool. We're talking core virtio and virtio-net here, and we know these are absolutely critical components in any virtualized environment; this has the potential to impact a lot of deployments. The relevant config options are `VIRTIO_NET`, `VIRTIO_PCI`, and `VIRTIO`, so those need to be verified in the target builds. I see the key symbols identified: `virtnet_set_rxnfc`, `virtnet_get_rxnfc`, the admin cap functions (`virtio_admin_cap_id_list_query`, `virtio_admin_cap_get`, `virtio_admin_cap_set`, `virtio_admin_obj_create`, `virtio_admin_obj_destroy`), and `vp_modern_avq_work`. I'm making a mental note to double-check that `vp_modern_avq_work` is static to prevent any potential linking issues. We need to be absolutely certain of this because of the scope of this project. |
| 6/2 | 2026/08/16 12:48 | tool | grepper |
0mArgs:{
"Expression": "vp_modern_avq_work"
}Results: {
"Output": "drivers/virtio/virtio_pci_modern.c=47=void vp_modern_avq_done(struct virtqueue *vq)\n--\ndrivers/virtio/virtio_pci_modern.c-53-\ndrivers/virtio/virtio_pci_modern.c:54:static void vp_modern_avq_work(struct work_struct *work)\ndrivers/virtio/virtio_pci_modern.c-55-{\n--\ndrivers/virtio/virtio_pci_modern.c=1276=int virtio_pci_modern_probe(struct virtio_pci_device *vp_dev)\n--\ndrivers/virtio/virtio_pci_modern.c-1299-\tmutex_init(\u0026vp_dev-\u003eadmin_vq.lock);\ndrivers/virtio/virtio_pci_modern.c:1300:\tINIT_WORK(\u0026vp_dev-\u003eadmin_vq.work, vp_modern_avq_work);\ndrivers/virtio/virtio_pci_modern.c-1301-\n"
} |
| 7/2 | 2026/08/16 12:48 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 36305 output: 24 thoughts: 53 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, as if they were my own: **Async Command Dispatch in a Workqueue Context** Right, so this is a static function, which is a bit of a constraint. However, being a callback function within the workqueue context, this is actually a pretty solid entry point for kicking off the asynchronous execution of admin commands. It's a convenient and well-defined point to start the asynchronous processing needed for some of these potentially longer-running operations. Now, let me just quickly confirm if that `virtnet_ethtool_flow_insert` function is also declared as `static`. Gotta make sure we're playing by the rules here and understand the full scope of any potential side effects or limitations. |
| 8/2 | 2026/08/16 12:48 | tool | grepper |
0mArgs:{
"Expression": "virtnet_ethtool_flow_insert"
}Results: {
"Output": "drivers/net/virtio_net.c=6408=static int build_and_insert(struct virtnet_ff *ff,\n--\ndrivers/net/virtio_net.c-6495-\ndrivers/net/virtio_net.c:6496:static int virtnet_ethtool_flow_insert(struct virtnet_ff *ff,\ndrivers/net/virtio_net.c-6497-\t\t\t\t struct ethtool_rx_flow_spec *fs,\n--\ndrivers/net/virtio_net.c=6651=static int virtnet_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info)\n--\ndrivers/net/virtio_net.c-6656-\tcase ETHTOOL_SRXCLSRLINS:\ndrivers/net/virtio_net.c:6657:\t\treturn virtnet_ethtool_flow_insert(\u0026vi-\u003eff, \u0026info-\u003efs,\ndrivers/net/virtio_net.c-6658-\t\t\t\t\t\t vi-\u003ecurr_queue_pairs);\n"
} |
| 9/2 | 2026/08/16 12:48 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 36825 output: 224 thoughts: 130 Thoughts: Okay, here's my take on those code elements and what I'm thinking about them: **Considering Key VirtNet & Virtio Functionality** Right, so I need to make sure I include the right pieces for this. Let's start with the ethtool entry points: `virtnet_set_rxnfc` and `virtnet_get_rxnfc`. They're critical. Then, I also need to incorporate the new API calls associated with virtio admin commands. I'm thinking specifically about `virtio_admin_cap_id_list_query`, `virtio_admin_cap_get`, and `virtio_admin_cap_set` for the capabilities side of things. Also, I'll add `virtio_admin_obj_create` and `virtio_admin_obj_destroy`, which are central to handling the objects. Finally, I can't forget about the workqueue handler, `vp_modern_avq_work`. That's important for asynchronous tasks. I'll keep this in mind as I build out the pieces of this project, I'll refer to this as set-results. |
| 10/2 | 2026/08/16 12:48 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"VIRTIO_NET",
"VIRTIO_PCI"
],
"FocusSymbols": [
"virtnet_set_rxnfc",
"virtnet_get_rxnfc",
"virtio_admin_cap_id_list_query",
"virtio_admin_cap_get",
"virtio_admin_cap_set",
"virtio_admin_obj_create",
"virtio_admin_obj_destroy",
"vp_modern_avq_work"
],
"Reasoning": "The patch introduces flow filter support (ethtool rxnfc) to the virtio-net driver, utilizing the new virtio admin command interface. This is a significant functional addition to a core virtualized network driver that is highly reachable in virtualized environments like QEMU/GCE. The changes include new ethtool callbacks and a new virtio admin command API, which should be fuzzed to ensure robustness.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"VIRTIO_NET",
"VIRTIO_PCI"
],
"FocusSymbols": [
"virtnet_set_rxnfc",
"virtnet_get_rxnfc",
"virtio_admin_cap_id_list_query",
"virtio_admin_cap_get",
"virtio_admin_cap_set",
"virtio_admin_obj_create",
"virtio_admin_obj_destroy",
"vp_modern_avq_work"
],
"Reasoning": "The patch introduces flow filter support (ethtool rxnfc) to the virtio-net driver, utilizing the new virtio admin command interface. This is a significant functional addition to a core virtualized network driver that is highly reachable in virtualized environments like QEMU/GCE. The changes include new ethtool callbacks and a new virtio admin command API, which should be fuzzed to ensure robustness.",
"WorthFuzzing": true
} |
| 11/1 | 2026/08/16 12:48 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 12/2 | 2026/08/16 12:48 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch series introduces flow filter support (ethtool rxnfc) for virtio-net. It adds new structures and functions to handle virtio admin commands and flow filter rules. \n\nA careful review of the memory allocations and data structures introduced by this patch shows that all new memory allocations for keys, masks, classifiers, rules, and admin command data are performed using `kzalloc` or `kzalloc_obj`, ensuring they are fully zero-initialized. For example:\n- `key = kzalloc(key_size, GFP_KERNEL);`\n- `c = kzalloc(classifier_size + ..., GFP_KERNEL);`\n- `ff_rule = kzalloc(sizeof(*ff_rule) + key_size, GFP_KERNEL);`\n- `data = kzalloc_obj(*data);` in admin command functions.\n\nFurthermore, local structures like `struct virtio_admin_cmd cmd = {};` and `struct virtio_net_resource_obj_ff_group ethtool_group = {};` are explicitly zero-initialized.\n\nWhen copying data back to user space via ethtool (`virtnet_ethtool_get_flow`, `virtnet_ethtool_get_all_flows`), the code only copies back the `flow_spec` that was previously provided by the user and stored in the fully zeroed `eth_rule` structure.\n\nSince all memory introduced or modified by this patch is properly initialized before being used, sent to the device, or copied to user space, there is no risk of uninitialized memory reads or info-leaks. Standard KASAN is sufficient to catch any potential out-of-bounds accesses or use-after-free bugs in this code.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: For your convenience, here is the diff of the changes:
commit 909d8d1fc9466a0a8af9348a0aec89b0e820e9e7
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Aug 16 12:47:41 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c
index aecfba7deeb03..7247f6cbdb32d 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_fs_ethtool.c
@@ -53,11 +53,6 @@ struct mlx5e_ethtool_steering {
static int flow_type_to_traffic_type(u32 flow_type);
-static u32 flow_type_mask(u32 flow_type)
-{
- return flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS);
-}
-
struct mlx5e_ethtool_rule {
struct list_head list;
struct ethtool_rx_flow_spec flow_spec;
@@ -91,7 +86,7 @@ static struct mlx5e_ethtool_table *get_flow_table(struct mlx5e_priv *priv,
int table_size;
int prio;
- switch (flow_type_mask(fs->flow_type)) {
+ switch (ethtool_flow_type_mask(fs->flow_type)) {
case TCP_V4_FLOW:
case UDP_V4_FLOW:
case TCP_V6_FLOW:
@@ -350,7 +345,7 @@ static int set_flow_attrs(u32 *match_c, u32 *match_v,
outer_headers);
void *outer_headers_v = MLX5_ADDR_OF(fte_match_param, match_v,
outer_headers);
- u32 flow_type = flow_type_mask(fs->flow_type);
+ u32 flow_type = ethtool_flow_type_mask(fs->flow_type);
switch (flow_type) {
case TCP_V4_FLOW:
@@ -435,7 +430,7 @@ static int flow_get_tirn(struct mlx5e_priv *priv,
if (!rss)
return -ENOENT;
- flow_type = flow_type_mask(fs->flow_type);
+ flow_type = ethtool_flow_type_mask(fs->flow_type);
tt = flow_type_to_traffic_type(flow_type);
if (tt < 0)
return -EINVAL;
@@ -673,7 +668,7 @@ static int validate_flow(struct mlx5e_priv *priv,
if (fs->ring_cookie >= priv->channels.params.num_channels)
return -EINVAL;
- switch (flow_type_mask(fs->flow_type)) {
+ switch (ethtool_flow_type_mask(fs->flow_type)) {
case ETHER_FLOW:
num_tuples += validate_ethter(fs);
break;
@@ -906,7 +901,7 @@ int mlx5e_ethtool_set_rxfh_fields(struct mlx5e_priv *priv,
rss_idx = nfc->rss_context;
- flow_type = flow_type_mask(nfc->flow_type);
+ flow_type = ethtool_flow_type_mask(nfc->flow_type);
tt = flow_type_to_traffic_type(flow_type);
if (tt < 0)
return tt;
@@ -951,7 +946,7 @@ int mlx5e_ethtool_get_rxfh_fields(struct mlx5e_priv *priv,
rss_idx = nfc->rss_context;
- flow_type = flow_type_mask(nfc->flow_type);
+ flow_type = ethtool_flow_type_mask(nfc->flow_type);
tt = flow_type_to_traffic_type(flow_type);
if (tt < 0)
return tt;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c
index 01ddc3def9ac0..83eaceddc4375 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c
@@ -233,11 +233,6 @@ static int mlx5i_get_link_ksettings(struct net_device *netdev,
return 0;
}
-static u32 mlx5i_flow_type_mask(u32 flow_type)
-{
- return flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS);
-}
-
static int mlx5i_set_rxfh_fields(struct net_device *dev,
const struct ethtool_rxfh_fields *cmd,
struct netlink_ext_ack *extack)
@@ -260,7 +255,7 @@ static int mlx5i_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
struct mlx5e_priv *priv = mlx5i_epriv(dev);
struct ethtool_rx_flow_spec *fs = &cmd->fs;
- if (mlx5i_flow_type_mask(fs->flow_type) == ETHER_FLOW)
+ if (ethtool_flow_type_mask(fs->flow_type) == ETHER_FLOW)
return -EINVAL;
return mlx5e_ethtool_set_rxnfc(priv, cmd);
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index 3e2a5876c6c8c..87365ca05c259 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -27,6 +27,15 @@
#include <net/netdev_queues.h>
#include <net/xdp_sock_drv.h>
#include <net/page_pool/helpers.h>
+#include <linux/virtio_admin.h>
+#include <net/ipv6.h>
+#include <net/ip.h>
+#include <uapi/linux/virtio_net_ff.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/xarray.h>
+#include <linux/refcount.h>
+#include <linux/unaligned.h>
static int napi_weight = NAPI_POLL_WEIGHT;
module_param(napi_weight, int, 0444);
@@ -282,6 +291,24 @@ static const struct virtnet_stat_desc virtnet_stats_tx_speed_desc_qstat[] = {
VIRTNET_STATS_DESC_TX_QSTAT(speed, ratelimit_packets, hw_drop_ratelimits),
};
+struct virtnet_ethtool_ff {
+ struct xarray rules;
+ int num_rules;
+};
+
+#define VIRTNET_FF_ETHTOOL_GROUP_PRIORITY 0
+#define VIRTNET_FF_MAX_GROUPS 1
+
+struct virtnet_ff {
+ struct virtio_device *vdev;
+ bool ff_supported;
+ struct virtio_net_ff_cap_data *ff_caps;
+ struct virtio_net_ff_cap_mask_data *ff_mask;
+ struct virtio_net_ff_actions *ff_actions;
+ struct xarray classifiers;
+ struct virtnet_ethtool_ff ethtool;
+};
+
#define VIRTNET_Q_TYPE_RX 0
#define VIRTNET_Q_TYPE_TX 1
#define VIRTNET_Q_TYPE_CQ 2
@@ -474,6 +501,8 @@ struct virtnet_info {
struct virtio_net_rss_config_hdr *rss_hdr;
+ struct virtnet_ff ff;
+
/* Must be last as it ends in a flexible-array member. */
TRAILING_OVERLAP(struct virtio_net_rss_config_trailer, rss_trailer, hash_key_data,
u8 rss_hash_key_data[NETDEV_RSS_KEY_LEN];
@@ -519,6 +548,7 @@ static struct sk_buff *virtnet_skb_append_frag(struct receive_queue *rq,
static void virtnet_xsk_completed(struct send_queue *sq, int num);
static void free_unused_bufs(struct virtnet_info *vi);
static void virtnet_del_vqs(struct virtnet_info *vi);
+static void remove_vq_common(struct virtnet_info *vi);
enum virtnet_xmit_type {
VIRTNET_XMIT_TYPE_SKB,
@@ -5587,34 +5617,6 @@ static u32 virtnet_get_rx_ring_count(struct net_device *dev)
return vi->curr_queue_pairs;
}
-static const struct ethtool_ops virtnet_ethtool_ops = {
- .supported_coalesce_params = ETHTOOL_COALESCE_MAX_FRAMES |
- ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_USE_ADAPTIVE_RX,
- .get_drvinfo = virtnet_get_drvinfo,
- .get_link = ethtool_op_get_link,
- .get_ringparam = virtnet_get_ringparam,
- .set_ringparam = virtnet_set_ringparam,
- .get_strings = virtnet_get_strings,
- .get_sset_count = virtnet_get_sset_count,
- .get_ethtool_stats = virtnet_get_ethtool_stats,
- .set_channels = virtnet_set_channels,
- .get_channels = virtnet_get_channels,
- .get_ts_info = ethtool_op_get_ts_info,
- .get_link_ksettings = virtnet_get_link_ksettings,
- .set_link_ksettings = virtnet_set_link_ksettings,
- .set_coalesce = virtnet_set_coalesce,
- .get_coalesce = virtnet_get_coalesce,
- .set_per_queue_coalesce = virtnet_set_per_queue_coalesce,
- .get_per_queue_coalesce = virtnet_get_per_queue_coalesce,
- .get_rxfh_key_size = virtnet_get_rxfh_key_size,
- .get_rxfh_indir_size = virtnet_get_rxfh_indir_size,
- .get_rxfh = virtnet_get_rxfh,
- .set_rxfh = virtnet_set_rxfh,
- .get_rxfh_fields = virtnet_get_hashflow,
- .set_rxfh_fields = virtnet_set_hashflow,
- .get_rx_ring_count = virtnet_get_rx_ring_count,
-};
-
static void virtnet_get_queue_stats_rx(struct net_device *dev, int i,
struct netdev_queue_stats_rx *stats)
{
@@ -5710,213 +5712,1423 @@ static const struct netdev_stat_ops virtnet_stat_ops = {
.get_base_stats = virtnet_get_base_stats,
};
-static void virtnet_freeze_down(struct virtio_device *vdev)
-{
- struct virtnet_info *vi = vdev->priv;
+struct virtnet_ethtool_rule {
+ struct ethtool_rx_flow_spec flow_spec;
+ u32 classifier_id;
+};
- /* Make sure no work handler is accessing the device */
- flush_work(&vi->config_work);
- disable_rx_mode_work(vi);
- flush_work(&vi->rx_mode_work);
+/* The classifier struct must be the last field in this struct */
+struct virtnet_classifier {
+ size_t size;
+ refcount_t refcount;
+ u32 id;
+ struct virtio_net_resource_obj_ff_classifier obj;
+};
- if (netif_running(vi->dev)) {
- rtnl_lock();
- virtnet_close(vi->dev);
- rtnl_unlock();
+static_assert(sizeof(struct virtnet_classifier) ==
+ ALIGN(offsetofend(struct virtnet_classifier, obj),
+ __alignof__(struct virtnet_classifier)),
+ "virtnet_classifier: classifier must be the last member");
+
+static bool check_mask_vs_cap(const void *m, const void *c,
+ u16 len, bool partial)
+{
+ const u8 *mask = m;
+ const u8 *cap = c;
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (partial && ((mask[i] & cap[i]) != mask[i]))
+ return false;
+ if (!partial && mask[i] != cap[i])
+ return false;
}
- netif_tx_lock_bh(vi->dev);
- netif_device_detach(vi->dev);
- netif_tx_unlock_bh(vi->dev);
+ return true;
}
-static int init_vqs(struct virtnet_info *vi);
+static
+struct virtio_net_ff_selector *get_selector_cap(const struct virtnet_ff *ff,
+ u8 selector_type)
+{
+ struct virtio_net_ff_selector *sel;
+ void *buf;
+ int i;
-static int virtnet_restore_up(struct virtio_device *vdev)
+ buf = &ff->ff_mask->selectors;
+ sel = buf;
+
+ for (i = 0; i < ff->ff_mask->count; i++) {
+ if (sel->type == selector_type)
+ return sel;
+
+ buf += sizeof(struct virtio_net_ff_selector) + sel->length;
+ sel = buf;
+ }
+
+ return NULL;
+}
+
+static bool validate_eth_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel,
+ const struct virtio_net_ff_selector *sel_cap)
{
- struct virtnet_info *vi = vdev->priv;
- int err;
+ bool partial_mask = !!(sel_cap->flags & VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);
+ struct ethhdr *cap, *mask;
+ struct ethhdr zeros = {};
- err = init_vqs(vi);
- if (err)
- return err;
+ cap = (struct ethhdr *)&sel_cap->mask;
+ mask = (struct ethhdr *)&sel->mask;
- err = virtnet_create_page_pools(vi);
- if (err)
- goto err_del_vqs;
+ if (memcmp(&zeros.h_dest, mask->h_dest, sizeof(zeros.h_dest)) &&
+ !check_mask_vs_cap(mask->h_dest, cap->h_dest,
+ sizeof(mask->h_dest), partial_mask))
+ return false;
- virtio_device_ready(vdev);
+ if (memcmp(&zeros.h_source, mask->h_source, sizeof(zeros.h_source)) &&
+ !check_mask_vs_cap(mask->h_source, cap->h_source,
+ sizeof(mask->h_source), partial_mask))
+ return false;
- enable_rx_mode_work(vi);
+ if (mask->h_proto &&
+ !check_mask_vs_cap(&mask->h_proto, &cap->h_proto,
+ sizeof(__be16), partial_mask))
+ return false;
- if (netif_running(vi->dev)) {
- rtnl_lock();
- err = virtnet_open(vi->dev);
- rtnl_unlock();
- if (err)
- goto err_destroy_pools;
- }
+ return true;
+}
- netif_tx_lock_bh(vi->dev);
- netif_device_attach(vi->dev);
- netif_tx_unlock_bh(vi->dev);
- return 0;
+static bool validate_ip4_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel,
+ const struct virtio_net_ff_selector *sel_cap)
+{
+ bool partial_mask = !!(sel_cap->flags & VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);
+ struct iphdr *cap, *mask;
-err_destroy_pools:
- virtio_reset_device(vdev);
- free_unused_bufs(vi);
- virtnet_destroy_page_pools(vi);
- virtnet_del_vqs(vi);
- return err;
+ cap = (struct iphdr *)&sel_cap->mask;
+ mask = (struct iphdr *)&sel->mask;
-err_del_vqs:
- virtio_reset_device(vdev);
- virtnet_del_vqs(vi);
- return err;
+ if (get_unaligned(&mask->saddr) &&
+ !check_mask_vs_cap(&mask->saddr, &cap->saddr,
+ sizeof(__be32), partial_mask))
+ return false;
+
+ if (get_unaligned(&mask->daddr) &&
+ !check_mask_vs_cap(&mask->daddr, &cap->daddr,
+ sizeof(__be32), partial_mask))
+ return false;
+
+ if (mask->protocol &&
+ !check_mask_vs_cap(&mask->protocol, &cap->protocol,
+ sizeof(u8), partial_mask))
+ return false;
+
+ if (mask->tos &&
+ !check_mask_vs_cap(&mask->tos, &cap->tos,
+ sizeof(u8), partial_mask))
+ return false;
+
+ return true;
}
-static int virtnet_set_guest_offloads(struct virtnet_info *vi, u64 offloads)
+static bool validate_ip6_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel,
+ const struct virtio_net_ff_selector *sel_cap)
{
- __virtio64 *_offloads __free(kfree) = NULL;
- struct scatterlist sg;
+ bool partial_mask = !!(sel_cap->flags & VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);
+ struct in6_addr tmp;
+ struct ipv6hdr *cap, *mask;
- _offloads = kzalloc_obj(*_offloads);
- if (!_offloads)
- return -ENOMEM;
+ cap = (struct ipv6hdr *)&sel_cap->mask;
+ mask = (struct ipv6hdr *)&sel->mask;
- *_offloads = cpu_to_virtio64(vi->vdev, offloads);
+ /* mask->saddr/daddr may be unaligned; copy to aligned tmp for
+ * ipv6_addr_any().
+ */
+ memcpy(&tmp, &mask->saddr, sizeof(tmp));
+ if (!ipv6_addr_any(&tmp) &&
+ !check_mask_vs_cap(&mask->saddr, &cap->saddr,
+ sizeof(cap->saddr), partial_mask))
+ return false;
- sg_init_one(&sg, _offloads, sizeof(*_offloads));
+ memcpy(&tmp, &mask->daddr, sizeof(tmp));
+ if (!ipv6_addr_any(&tmp) &&
+ !check_mask_vs_cap(&mask->daddr, &cap->daddr,
+ sizeof(cap->daddr), partial_mask))
+ return false;
- if (!virtnet_send_command(vi, VIRTIO_NET_CTRL_GUEST_OFFLOADS,
- VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, &sg)) {
- dev_warn(&vi->dev->dev, "Fail to set guest offload.\n");
- return -EINVAL;
- }
+ if (mask->nexthdr &&
+ !check_mask_vs_cap(&mask->nexthdr, &cap->nexthdr,
+ sizeof(cap->nexthdr), partial_mask))
+ return false;
- return 0;
+ return true;
}
-static int virtnet_clear_guest_offloads(struct virtnet_info *vi)
+static bool validate_tcp_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel,
+ const struct virtio_net_ff_selector *sel_cap)
{
- u64 offloads = 0;
+ bool partial_mask = !!(sel_cap->flags & VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);
+ struct tcphdr *cap, *mask;
- if (!vi->guest_offloads)
- return 0;
+ cap = (struct tcphdr *)&sel_cap->mask;
+ mask = (struct tcphdr *)&sel->mask;
- return virtnet_set_guest_offloads(vi, offloads);
+ if (get_unaligned(&mask->source) &&
+ !check_mask_vs_cap(&mask->source, &cap->source,
+ sizeof(cap->source), partial_mask))
+ return false;
+
+ if (get_unaligned(&mask->dest) &&
+ !check_mask_vs_cap(&mask->dest, &cap->dest,
+ sizeof(cap->dest), partial_mask))
+ return false;
+
+ return true;
}
-static int virtnet_restore_guest_offloads(struct virtnet_info *vi)
+static bool validate_udp_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel,
+ const struct virtio_net_ff_selector *sel_cap)
{
- u64 offloads = vi->guest_offloads;
+ bool partial_mask = !!(sel_cap->flags & VIRTIO_NET_FF_MASK_F_PARTIAL_MASK);
+ struct udphdr *cap, *mask;
- if (!vi->guest_offloads)
- return 0;
+ cap = (struct udphdr *)&sel_cap->mask;
+ mask = (struct udphdr *)&sel->mask;
- return virtnet_set_guest_offloads(vi, offloads);
+ if (get_unaligned(&mask->source) &&
+ !check_mask_vs_cap(&mask->source, &cap->source,
+ sizeof(cap->source), partial_mask))
+ return false;
+
+ if (get_unaligned(&mask->dest) &&
+ !check_mask_vs_cap(&mask->dest, &cap->dest,
+ sizeof(cap->dest), partial_mask))
+ return false;
+
+ return true;
}
-static int virtnet_rq_bind_xsk_pool(struct virtnet_info *vi, struct receive_queue *rq,
- struct xsk_buff_pool *pool)
+static bool validate_mask(const struct virtnet_ff *ff,
+ const struct virtio_net_ff_selector *sel)
{
- int err, qindex;
+ struct virtio_net_ff_selector *sel_cap = get_selector_cap(ff, sel->type);
- qindex = rq - vi->rq;
+ if (!sel_cap)
+ return false;
- if (pool) {
- err = xdp_rxq_info_reg(&rq->xsk_rxq_info, vi->dev, qindex, rq->napi.napi_id);
- if (err < 0)
- return err;
+ switch (sel->type) {
+ case VIRTIO_NET_FF_MASK_TYPE_ETH:
+ return validate_eth_mask(ff, sel, sel_cap);
- err = xdp_rxq_info_reg_mem_model(&rq->xsk_rxq_info,
- MEM_TYPE_XSK_BUFF_POOL, NULL);
- if (err < 0)
- goto unreg;
+ case VIRTIO_NET_FF_MASK_TYPE_IPV4:
+ return validate_ip4_mask(ff, sel, sel_cap);
- xsk_pool_set_rxq_info(pool, &rq->xsk_rxq_info);
+ case VIRTIO_NET_FF_MASK_TYPE_IPV6:
+ return validate_ip6_mask(ff, sel, sel_cap);
+
+ case VIRTIO_NET_FF_MASK_TYPE_TCP:
+ return validate_tcp_mask(ff, sel, sel_cap);
+
+ case VIRTIO_NET_FF_MASK_TYPE_UDP:
+ return validate_udp_mask(ff, sel, sel_cap);
}
- virtnet_rx_pause(vi, rq);
+ return false;
+}
- err = virtqueue_reset(rq->vq, virtnet_rq_unmap_free_buf, NULL);
- if (err) {
- netdev_err(vi->dev, "reset rx fail: rx queue index: %d err: %d\n", qindex, err);
+static void set_tcp(struct tcphdr *mask, struct tcphdr *key,
+ __be16 psrc_m, __be16 psrc_k,
+ __be16 pdst_m, __be16 pdst_k)
+{
+ /* mask/key may be unaligned; use memcpy */
+ if (psrc_m) {
+ memcpy(&mask->source, &psrc_m, sizeof(mask->source));
+ memcpy(&key->source, &psrc_k, sizeof(key->source));
+ }
+ if (pdst_m) {
+ memcpy(&mask->dest, &pdst_m, sizeof(mask->dest));
+ memcpy(&key->dest, &pdst_k, sizeof(key->dest));
+ }
+}
- pool = NULL;
+static void set_udp(struct udphdr *mask, struct udphdr *key,
+ __be16 psrc_m, __be16 psrc_k,
+ __be16 pdst_m, __be16 pdst_k)
+{
+ /* mask/key may be unaligned; use memcpy */
+ if (psrc_m) {
+ memcpy(&mask->source, &psrc_m, sizeof(mask->source));
+ memcpy(&key->source, &psrc_k, sizeof(key->source));
+ }
+ if (pdst_m) {
+ memcpy(&mask->dest, &pdst_m, sizeof(mask->dest));
+ memcpy(&key->dest, &pdst_k, sizeof(key->dest));
}
+}
- rq->xsk_pool = pool;
+static void parse_ip4(struct iphdr *mask, struct iphdr *key,
+ const struct ethtool_rx_flow_spec *fs)
+{
+ const struct ethtool_usrip4_spec *l3_mask = &fs->m_u.usr_ip4_spec;
+ const struct ethtool_usrip4_spec *l3_val = &fs->h_u.usr_ip4_spec;
- virtnet_rx_resume(vi, rq, true);
+ if (l3_mask->ip4src) {
+ put_unaligned(l3_mask->ip4src, &mask->saddr);
+ put_unaligned(l3_val->ip4src, &key->saddr);
+ }
- if (pool)
- return 0;
+ if (l3_mask->ip4dst) {
+ put_unaligned(l3_mask->ip4dst, &mask->daddr);
+ put_unaligned(l3_val->ip4dst, &key->daddr);
+ }
-unreg:
- xdp_rxq_info_unreg(&rq->xsk_rxq_info);
- return err;
+ if (l3_mask->tos) {
+ mask->tos = l3_mask->tos;
+ key->tos = l3_val->tos;
+ }
}
-static int virtnet_sq_bind_xsk_pool(struct virtnet_info *vi,
- struct send_queue *sq,
- struct xsk_buff_pool *pool)
+static void parse_ip6(struct ipv6hdr *mask, struct ipv6hdr *key,
+ const struct ethtool_rx_flow_spec *fs)
{
- int err, qindex;
-
- qindex = sq - vi->sq;
+ const struct ethtool_usrip6_spec *l3_mask = &fs->m_u.usr_ip6_spec;
+ const struct ethtool_usrip6_spec *l3_val = &fs->h_u.usr_ip6_spec;
- virtnet_tx_pause(vi, sq);
+ if (!ipv6_addr_any((struct in6_addr *)l3_mask->ip6src)) {
+ memcpy(&mask->saddr, l3_mask->ip6src, sizeof(mask->saddr));
+ memcpy(&key->saddr, l3_val->ip6src, sizeof(key->saddr));
+ }
- err = virtqueue_reset(sq->vq, virtnet_sq_free_unused_buf,
- virtnet_sq_free_unused_buf_done);
- if (err) {
- netdev_err(vi->dev, "reset tx fail: tx queue index: %d err: %d\n", qindex, err);
- pool = NULL;
+ if (!ipv6_addr_any((struct in6_addr *)l3_mask->ip6dst)) {
+ memcpy(&mask->daddr, l3_mask->ip6dst, sizeof(mask->daddr));
+ memcpy(&key->daddr, l3_val->ip6dst, sizeof(key->daddr));
}
+}
- sq->xsk_pool = pool;
+static bool has_ipv4(u32 flow_type)
+{
+ return flow_type == TCP_V4_FLOW ||
+ flow_type == UDP_V4_FLOW ||
+ flow_type == IP_USER_FLOW;
+}
- virtnet_tx_resume(vi, sq);
+static bool has_ipv6(u32 flow_type)
+{
+ return flow_type == TCP_V6_FLOW ||
+ flow_type == UDP_V6_FLOW ||
+ flow_type == IPV6_USER_FLOW;
+}
- return err;
+static bool has_tcp(u32 flow_type)
+{
+ return flow_type == TCP_V4_FLOW || flow_type == TCP_V6_FLOW;
}
-static int virtnet_xsk_pool_enable(struct net_device *dev,
- struct xsk_buff_pool *pool,
- u16 qid)
+static bool has_udp(u32 flow_type)
{
- struct virtnet_info *vi = netdev_priv(dev);
- struct receive_queue *rq;
- struct device *dma_dev;
- struct send_queue *sq;
- dma_addr_t hdr_dma;
- int err, size;
+ return flow_type == UDP_V4_FLOW || flow_type == UDP_V6_FLOW;
+}
- if (vi->hdr_len > xsk_pool_get_headroom(pool))
- return -EINVAL;
+static int setup_classifier(struct virtnet_ff *ff,
+ struct virtnet_classifier **c)
+{
+ struct virtnet_classifier *tmp;
+ unsigned long i;
+ int err;
- /* In big_packets mode, xdp cannot work, so there is no need to
- * initialize xsk of rq.
- */
- if (!vi->rq[qid].page_pool)
- return -ENOENT;
+ xa_for_each(&ff->classifiers, i, tmp) {
+ if ((*c)->size == tmp->size &&
+ !memcmp(&tmp->obj, &(*c)->obj, tmp->size)) {
+ refcount_inc(&tmp->refcount);
+ kfree(*c);
+ *c = tmp;
+ goto out;
+ }
+ }
- if (qid >= vi->curr_queue_pairs)
- return -EINVAL;
+ err = xa_alloc(&ff->classifiers, &(*c)->id, *c,
+ XA_LIMIT(0, le32_to_cpu(ff->ff_caps->classifiers_limit) - 1),
+ GFP_KERNEL);
+ if (err)
+ return err;
- sq = &vi->sq[qid];
- rq = &vi->rq[qid];
+ err = virtio_admin_obj_create(ff->vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER,
+ (*c)->id,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0,
+ &(*c)->obj,
+ (*c)->size);
+ if (err)
+ goto err_xarray;
- /* xsk assumes that tx and rx must have the same dma device. The af-xdp
- * may use one buffer to receive from the rx and reuse this buffer to
- * send by the tx. So the dma dev of sq and rq must be the same one.
- *
- * But vq->dma_dev allows every vq has the respective dma dev. So I
- * check the dma dev of vq and sq is the same dev.
- */
- if (virtqueue_dma_dev(rq->vq) != virtqueue_dma_dev(sq->vq))
- return -EINVAL;
+ refcount_set(&(*c)->refcount, 1);
+out:
+ return 0;
+
+err_xarray:
+ xa_erase(&ff->classifiers, (*c)->id);
+
+ return err;
+}
+
+static void try_destroy_classifier(struct virtnet_ff *ff, u32 classifier_id)
+{
+ struct virtnet_classifier *c;
+
+ c = xa_load(&ff->classifiers, classifier_id);
+ if (c && refcount_dec_and_test(&c->refcount)) {
+ virtio_admin_obj_destroy(ff->vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER,
+ c->id,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0);
+
+ xa_erase(&ff->classifiers, c->id);
+ kfree(c);
+ }
+}
+
+static void destroy_ethtool_rule(struct virtnet_ff *ff,
+ struct virtnet_ethtool_rule *eth_rule)
+{
+ ff->ethtool.num_rules--;
+
+ virtio_admin_obj_destroy(ff->vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_RULE,
+ eth_rule->flow_spec.location,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0);
+
+ xa_erase(&ff->ethtool.rules, eth_rule->flow_spec.location);
+ try_destroy_classifier(ff, eth_rule->classifier_id);
+ kfree(eth_rule);
+}
+
+static int insert_rule(struct virtnet_ff *ff,
+ struct virtnet_ethtool_rule *eth_rule,
+ u32 classifier_id,
+ const u8 *key,
+ u8 key_size)
+{
+ struct ethtool_rx_flow_spec *fs = ð_rule->flow_spec;
+ struct virtio_net_resource_obj_ff_rule *ff_rule;
+ int err;
+
+ ff_rule = kzalloc(sizeof(*ff_rule) + key_size, GFP_KERNEL);
+ if (!ff_rule)
+ return -ENOMEM;
+
+ /* Intentionally leave the priority as 0. All rules have the same
+ * priority.
+ */
+ ff_rule->group_id = cpu_to_le32(VIRTNET_FF_ETHTOOL_GROUP_PRIORITY);
+ ff_rule->classifier_id = cpu_to_le32(classifier_id);
+ ff_rule->key_length = key_size;
+ ff_rule->action = fs->ring_cookie == RX_CLS_FLOW_DISC ?
+ VIRTIO_NET_FF_ACTION_DROP :
+ VIRTIO_NET_FF_ACTION_RX_VQ;
+ ff_rule->vq_index = fs->ring_cookie != RX_CLS_FLOW_DISC ?
+ cpu_to_le16(rxq2vq(fs->ring_cookie)) : 0;
+ memcpy(&ff_rule->keys, key, key_size);
+
+ err = virtio_admin_obj_create(ff->vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_RULE,
+ fs->location,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0,
+ ff_rule,
+ sizeof(*ff_rule) + key_size);
+ if (err)
+ goto err_ff_rule;
+
+ eth_rule->classifier_id = classifier_id;
+ ff->ethtool.num_rules++;
+ kfree(ff_rule);
+ kfree(key);
+
+ return 0;
+
+err_ff_rule:
+ kfree(ff_rule);
+
+ return err;
+}
+
+static bool supported_flow_type(const struct ethtool_rx_flow_spec *fs)
+{
+ switch (fs->flow_type) {
+ case ETHER_FLOW:
+ case IP_USER_FLOW:
+ case IPV6_USER_FLOW:
+ case TCP_V4_FLOW:
+ case TCP_V6_FLOW:
+ case UDP_V4_FLOW:
+ case UDP_V6_FLOW:
+ return true;
+ }
+
+ return false;
+}
+
+static int validate_flow_input(struct virtnet_ff *ff,
+ const struct ethtool_rx_flow_spec *fs,
+ u16 curr_queue_pairs)
+{
+ u8 required_action = fs->ring_cookie == RX_CLS_FLOW_DISC ?
+ VIRTIO_NET_FF_ACTION_DROP :
+ VIRTIO_NET_FF_ACTION_RX_VQ;
+ int i;
+
+ /* Force users to use RX_CLS_LOC_ANY - don't allow specific locations */
+ if (fs->location != RX_CLS_LOC_ANY)
+ return -EOPNOTSUPP;
+
+ if (fs->ring_cookie != RX_CLS_FLOW_DISC &&
+ fs->ring_cookie >= curr_queue_pairs)
+ return -EINVAL;
+
+ for (i = 0; i < ff->ff_actions->count; i++)
+ if (ff->ff_actions->actions[i] == required_action)
+ goto action_ok;
+ return -EOPNOTSUPP;
+
+action_ok:
+ if (fs->flow_type != ethtool_flow_type_mask(fs->flow_type))
+ return -EOPNOTSUPP;
+
+ if (!supported_flow_type(fs))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static void calculate_flow_sizes(struct ethtool_rx_flow_spec *fs,
+ u8 *key_size, size_t *classifier_size,
+ int *num_hdrs)
+{
+ size_t size = sizeof(struct ethhdr);
+
+ *num_hdrs = 1;
+
+ if (fs->flow_type != ETHER_FLOW) {
+ ++(*num_hdrs);
+ if (has_ipv4(fs->flow_type))
+ size += sizeof(struct iphdr);
+ else if (has_ipv6(fs->flow_type))
+ size += sizeof(struct ipv6hdr);
+
+ if (has_tcp(fs->flow_type) || has_udp(fs->flow_type)) {
+ ++(*num_hdrs);
+ size += has_tcp(fs->flow_type) ? sizeof(struct tcphdr) :
+ sizeof(struct udphdr);
+ }
+ }
+
+ BUG_ON(size > 0xff);
+ *key_size = size;
+ /*
+ * The classifier size is the size of the classifier header, a selector
+ * header for each type of header in the match criteria, and each header
+ * providing the mask for matching against.
+ */
+ *classifier_size = *key_size +
+ sizeof(struct virtio_net_resource_obj_ff_classifier) +
+ sizeof(struct virtio_net_ff_selector) * (*num_hdrs);
+}
+
+static void setup_eth_hdr_key_mask(struct virtio_net_ff_selector *selector,
+ u8 *key,
+ const struct ethtool_rx_flow_spec *fs,
+ int num_hdrs)
+{
+ struct ethhdr *eth_m = (struct ethhdr *)&selector->mask;
+ struct ethhdr *eth_k = (struct ethhdr *)key;
+
+ selector->type = VIRTIO_NET_FF_MASK_TYPE_ETH;
+ selector->length = sizeof(struct ethhdr);
+
+ if (num_hdrs > 1) {
+ eth_m->h_proto = cpu_to_be16(0xffff);
+ if (has_ipv4(fs->flow_type))
+ eth_k->h_proto = cpu_to_be16(ETH_P_IP);
+ else
+ eth_k->h_proto = cpu_to_be16(ETH_P_IPV6);
+ } else {
+ memcpy(eth_m, &fs->m_u.ether_spec, sizeof(*eth_m));
+ memcpy(eth_k, &fs->h_u.ether_spec, sizeof(*eth_k));
+ }
+}
+
+static int setup_ip_key_mask(struct virtio_net_ff_selector *selector,
+ u8 *key,
+ const struct ethtool_rx_flow_spec *fs,
+ int num_hdrs)
+{
+ struct ipv6hdr *v6_m = (struct ipv6hdr *)&selector->mask;
+ struct iphdr *v4_m = (struct iphdr *)&selector->mask;
+ struct ipv6hdr *v6_k = (struct ipv6hdr *)key;
+ struct iphdr *v4_k = (struct iphdr *)key;
+
+ if (has_ipv6(fs->flow_type)) {
+ selector->type = VIRTIO_NET_FF_MASK_TYPE_IPV6;
+ selector->length = sizeof(struct ipv6hdr);
+
+ /* exclude tclass, it's not exposed directly in struct ipv6hdr */
+ if (fs->h_u.usr_ip6_spec.tclass ||
+ fs->m_u.usr_ip6_spec.tclass ||
+ (num_hdrs == 2 && (fs->h_u.usr_ip6_spec.l4_4_bytes ||
+ fs->m_u.usr_ip6_spec.l4_4_bytes ||
+ fs->h_u.usr_ip6_spec.l4_proto ||
+ fs->m_u.usr_ip6_spec.l4_proto)))
+ return -EINVAL;
+
+ parse_ip6(v6_m, v6_k, fs);
+
+ if (num_hdrs > 2) {
+ v6_m->nexthdr = 0xff;
+ if (has_tcp(fs->flow_type))
+ v6_k->nexthdr = IPPROTO_TCP;
+ else
+ v6_k->nexthdr = IPPROTO_UDP;
+ }
+ } else {
+ selector->type = VIRTIO_NET_FF_MASK_TYPE_IPV4;
+ selector->length = sizeof(struct iphdr);
+
+ if (num_hdrs == 2 &&
+ (fs->h_u.usr_ip4_spec.l4_4_bytes ||
+ fs->h_u.usr_ip4_spec.ip_ver != ETH_RX_NFC_IP4 ||
+ fs->m_u.usr_ip4_spec.l4_4_bytes ||
+ fs->m_u.usr_ip4_spec.ip_ver ||
+ fs->m_u.usr_ip4_spec.proto))
+ return -EINVAL;
+
+ parse_ip4(v4_m, v4_k, fs);
+
+ if (num_hdrs > 2) {
+ v4_m->protocol = 0xff;
+ if (has_tcp(fs->flow_type))
+ v4_k->protocol = IPPROTO_TCP;
+ else
+ v4_k->protocol = IPPROTO_UDP;
+ }
+ }
+
+ return 0;
+}
+
+static int setup_transport_key_mask(struct virtio_net_ff_selector *selector,
+ u8 *key,
+ struct ethtool_rx_flow_spec *fs)
+{
+ struct tcphdr *tcp_m = (struct tcphdr *)&selector->mask;
+ struct udphdr *udp_m = (struct udphdr *)&selector->mask;
+ const struct ethtool_tcpip6_spec *v6_l4_mask;
+ const struct ethtool_tcpip4_spec *v4_l4_mask;
+ const struct ethtool_tcpip6_spec *v6_l4_key;
+ const struct ethtool_tcpip4_spec *v4_l4_key;
+ struct tcphdr *tcp_k = (struct tcphdr *)key;
+ struct udphdr *udp_k = (struct udphdr *)key;
+
+ if (has_tcp(fs->flow_type)) {
+ selector->type = VIRTIO_NET_FF_MASK_TYPE_TCP;
+ selector->length = sizeof(struct tcphdr);
+
+ if (has_ipv6(fs->flow_type)) {
+ v6_l4_mask = &fs->m_u.tcp_ip6_spec;
+ v6_l4_key = &fs->h_u.tcp_ip6_spec;
+
+ set_tcp(tcp_m, tcp_k, v6_l4_mask->psrc, v6_l4_key->psrc,
+ v6_l4_mask->pdst, v6_l4_key->pdst);
+ } else {
+ v4_l4_mask = &fs->m_u.tcp_ip4_spec;
+ v4_l4_key = &fs->h_u.tcp_ip4_spec;
+
+ set_tcp(tcp_m, tcp_k, v4_l4_mask->psrc, v4_l4_key->psrc,
+ v4_l4_mask->pdst, v4_l4_key->pdst);
+ }
+
+ } else if (has_udp(fs->flow_type)) {
+ selector->type = VIRTIO_NET_FF_MASK_TYPE_UDP;
+ selector->length = sizeof(struct udphdr);
+
+ if (has_ipv6(fs->flow_type)) {
+ v6_l4_mask = &fs->m_u.udp_ip6_spec;
+ v6_l4_key = &fs->h_u.udp_ip6_spec;
+
+ set_udp(udp_m, udp_k, v6_l4_mask->psrc, v6_l4_key->psrc,
+ v6_l4_mask->pdst, v6_l4_key->pdst);
+ } else {
+ v4_l4_mask = &fs->m_u.udp_ip4_spec;
+ v4_l4_key = &fs->h_u.udp_ip4_spec;
+
+ set_udp(udp_m, udp_k, v4_l4_mask->psrc, v4_l4_key->psrc,
+ v4_l4_mask->pdst, v4_l4_key->pdst);
+ }
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int
+validate_classifier_selectors(struct virtnet_ff *ff,
+ struct virtio_net_resource_obj_ff_classifier *classifier,
+ int num_hdrs)
+{
+ struct virtio_net_ff_selector *selector = (void *)classifier->selectors;
+ int i;
+
+ if (num_hdrs > ff->ff_caps->selectors_per_classifier_limit)
+ return -EINVAL;
+
+ for (i = 0; i < num_hdrs; i++) {
+ if (!validate_mask(ff, selector))
+ return -EINVAL;
+
+ selector = (((void *)selector) + sizeof(*selector) +
+ selector->length);
+ }
+
+ return 0;
+}
+
+static
+struct virtio_net_ff_selector *next_selector(struct virtio_net_ff_selector *sel)
+{
+ return (void *)sel + sizeof(struct virtio_net_ff_selector) +
+ sel->length;
+}
+
+static int build_and_insert(struct virtnet_ff *ff,
+ struct virtnet_ethtool_rule *eth_rule)
+{
+ struct virtio_net_resource_obj_ff_classifier *classifier;
+ struct ethtool_rx_flow_spec *fs = ð_rule->flow_spec;
+ struct virtio_net_ff_selector *selector;
+ struct virtnet_classifier *c;
+ size_t classifier_size;
+ size_t key_offset;
+ int num_hdrs;
+ u8 key_size;
+ u8 *key;
+ int err;
+
+ calculate_flow_sizes(fs, &key_size, &classifier_size, &num_hdrs);
+
+ key = kzalloc(key_size, GFP_KERNEL);
+ if (!key)
+ return -ENOMEM;
+
+ /*
+ * virtio_net_ff_obj_ff_classifier is already included in the
+ * classifier_size.
+ */
+ c = kzalloc(classifier_size +
+ sizeof(struct virtnet_classifier) -
+ sizeof(struct virtio_net_resource_obj_ff_classifier),
+ GFP_KERNEL);
+ if (!c) {
+ kfree(key);
+ return -ENOMEM;
+ }
+
+ c->size = classifier_size;
+ classifier = &c->obj;
+ classifier->count = num_hdrs;
+ selector = (void *)&classifier->selectors[0];
+
+ setup_eth_hdr_key_mask(selector, key, fs, num_hdrs);
+
+ if (has_ipv4(fs->flow_type) || has_ipv6(fs->flow_type)) {
+ key_offset = selector->length;
+ selector = next_selector(selector);
+
+ err = setup_ip_key_mask(selector, key + key_offset,
+ fs, num_hdrs);
+ if (err)
+ goto err_classifier;
+
+ if (has_udp(fs->flow_type) || has_tcp(fs->flow_type)) {
+ key_offset += selector->length;
+ selector = next_selector(selector);
+
+ err = setup_transport_key_mask(selector,
+ key + key_offset,
+ fs);
+ if (err)
+ goto err_classifier;
+ }
+ }
+
+ err = validate_classifier_selectors(ff, classifier, num_hdrs);
+ if (err)
+ goto err_classifier;
+
+ err = setup_classifier(ff, &c);
+ if (err)
+ goto err_classifier;
+
+ err = insert_rule(ff, eth_rule, c->id, key, key_size);
+ if (err) {
+ /* try_destroy_classifier will decrement the refcount on the
+ * classifier and free it if needed.
+ */
+ try_destroy_classifier(ff, c->id);
+ goto err_key;
+ }
+
+ return 0;
+
+err_classifier:
+ kfree(c);
+err_key:
+ kfree(key);
+
+ return err;
+}
+
+static int virtnet_ethtool_flow_insert(struct virtnet_ff *ff,
+ struct ethtool_rx_flow_spec *fs,
+ u16 curr_queue_pairs)
+{
+ struct virtnet_ethtool_rule *eth_rule;
+ int err;
+
+ if (!ff->ff_supported)
+ return -EOPNOTSUPP;
+
+ err = validate_flow_input(ff, fs, curr_queue_pairs);
+ if (err)
+ return err;
+
+ eth_rule = kzalloc(sizeof(*eth_rule), GFP_KERNEL);
+ if (!eth_rule)
+ return -ENOMEM;
+
+ err = xa_alloc(&ff->ethtool.rules, &fs->location, eth_rule,
+ XA_LIMIT(0, le32_to_cpu(ff->ff_caps->rules_limit) - 1),
+ GFP_KERNEL);
+ if (err)
+ goto err_rule;
+
+ eth_rule->flow_spec = *fs;
+
+ err = build_and_insert(ff, eth_rule);
+ if (err)
+ goto err_xa;
+
+ return err;
+
+err_xa:
+ xa_erase(&ff->ethtool.rules, eth_rule->flow_spec.location);
+
+err_rule:
+ fs->location = RX_CLS_LOC_ANY;
+ kfree(eth_rule);
+
+ return err;
+}
+
+static int virtnet_ethtool_flow_remove(struct virtnet_ff *ff, int location)
+{
+ struct virtnet_ethtool_rule *eth_rule;
+ int err = 0;
+
+ if (!ff->ff_supported)
+ return -EOPNOTSUPP;
+
+ eth_rule = xa_load(&ff->ethtool.rules, location);
+ if (!eth_rule) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ destroy_ethtool_rule(ff, eth_rule);
+out:
+ return err;
+}
+
+static int virtnet_ethtool_get_flow_count(struct virtnet_ff *ff,
+ struct ethtool_rxnfc *info)
+{
+ if (!ff->ff_supported)
+ return -EOPNOTSUPP;
+
+ info->rule_cnt = ff->ethtool.num_rules;
+ info->data = min(le32_to_cpu(ff->ff_caps->rules_limit),
+ le32_to_cpu(ff->ff_caps->rules_per_group_limit)) |
+ RX_CLS_LOC_SPECIAL;
+
+ return 0;
+}
+
+static int virtnet_ethtool_get_flow(struct virtnet_ff *ff,
+ struct ethtool_rxnfc *info)
+{
+ struct virtnet_ethtool_rule *eth_rule;
+
+ if (!ff->ff_supported)
+ return -EOPNOTSUPP;
+
+ eth_rule = xa_load(&ff->ethtool.rules, info->fs.location);
+ if (!eth_rule)
+ return -ENOENT;
+
+ info->fs = eth_rule->flow_spec;
+
+ return 0;
+}
+
+static int
+virtnet_ethtool_get_all_flows(struct virtnet_ff *ff,
+ struct ethtool_rxnfc *info, u32 *rule_locs)
+{
+ struct virtnet_ethtool_rule *eth_rule;
+ unsigned long i = 0;
+ int idx = 0;
+
+ if (!ff->ff_supported)
+ return -EOPNOTSUPP;
+
+ xa_for_each(&ff->ethtool.rules, i, eth_rule) {
+ if (idx == info->rule_cnt)
+ return -EMSGSIZE;
+ rule_locs[idx++] = i;
+ }
+
+ info->data = le32_to_cpu(ff->ff_caps->rules_limit);
+ info->rule_cnt = idx;
+
+ return 0;
+}
+
+static size_t get_mask_size(u16 type)
+{
+ switch (type) {
+ case VIRTIO_NET_FF_MASK_TYPE_ETH:
+ return sizeof(struct ethhdr);
+ case VIRTIO_NET_FF_MASK_TYPE_IPV4:
+ return sizeof(struct iphdr);
+ case VIRTIO_NET_FF_MASK_TYPE_IPV6:
+ return sizeof(struct ipv6hdr);
+ case VIRTIO_NET_FF_MASK_TYPE_TCP:
+ return sizeof(struct tcphdr);
+ case VIRTIO_NET_FF_MASK_TYPE_UDP:
+ return sizeof(struct udphdr);
+ }
+
+ return 0;
+}
+
+static int virtnet_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info, u32 *rule_locs)
+{
+ struct virtnet_info *vi = netdev_priv(dev);
+ int rc;
+
+ switch (info->cmd) {
+ case ETHTOOL_GRXCLSRLCNT:
+ rc = virtnet_ethtool_get_flow_count(&vi->ff, info);
+ break;
+ case ETHTOOL_GRXCLSRULE:
+ rc = virtnet_ethtool_get_flow(&vi->ff, info);
+ break;
+ case ETHTOOL_GRXCLSRLALL:
+ rc = virtnet_ethtool_get_all_flows(&vi->ff, info, rule_locs);
+ break;
+ default:
+ rc = -EOPNOTSUPP;
+ }
+
+ return rc;
+}
+
+static int virtnet_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info)
+{
+ struct virtnet_info *vi = netdev_priv(dev);
+
+ switch (info->cmd) {
+ case ETHTOOL_SRXCLSRLINS:
+ return virtnet_ethtool_flow_insert(&vi->ff, &info->fs,
+ vi->curr_queue_pairs);
+ case ETHTOOL_SRXCLSRLDEL:
+ return virtnet_ethtool_flow_remove(&vi->ff, info->fs.location);
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static const struct ethtool_ops virtnet_ethtool_ops = {
+ .supported_coalesce_params = ETHTOOL_COALESCE_MAX_FRAMES |
+ ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_USE_ADAPTIVE_RX,
+ .get_drvinfo = virtnet_get_drvinfo,
+ .get_link = ethtool_op_get_link,
+ .get_ringparam = virtnet_get_ringparam,
+ .set_ringparam = virtnet_set_ringparam,
+ .get_strings = virtnet_get_strings,
+ .get_sset_count = virtnet_get_sset_count,
+ .get_ethtool_stats = virtnet_get_ethtool_stats,
+ .set_channels = virtnet_set_channels,
+ .get_channels = virtnet_get_channels,
+ .get_ts_info = ethtool_op_get_ts_info,
+ .get_link_ksettings = virtnet_get_link_ksettings,
+ .set_link_ksettings = virtnet_set_link_ksettings,
+ .set_coalesce = virtnet_set_coalesce,
+ .get_coalesce = virtnet_get_coalesce,
+ .set_per_queue_coalesce = virtnet_set_per_queue_coalesce,
+ .get_per_queue_coalesce = virtnet_get_per_queue_coalesce,
+ .get_rxfh_key_size = virtnet_get_rxfh_key_size,
+ .get_rxfh_indir_size = virtnet_get_rxfh_indir_size,
+ .get_rxfh = virtnet_get_rxfh,
+ .set_rxfh = virtnet_set_rxfh,
+ .get_rxfh_fields = virtnet_get_hashflow,
+ .set_rxfh_fields = virtnet_set_hashflow,
+ .get_rx_ring_count = virtnet_get_rx_ring_count,
+ .get_rxnfc = virtnet_get_rxnfc,
+ .set_rxnfc = virtnet_set_rxnfc,
+};
+
+static int virtnet_ff_init(struct virtnet_ff *ff, struct virtio_device *vdev)
+{
+ size_t ff_mask_size = sizeof(struct virtio_net_ff_cap_mask_data) +
+ sizeof(struct virtio_net_ff_selector) *
+ VIRTIO_NET_FF_MASK_TYPE_MAX;
+ struct virtio_net_resource_obj_ff_group ethtool_group = {};
+ struct virtio_admin_cmd_query_cap_id_result *cap_id_list;
+ struct virtio_net_ff_selector *sel;
+ unsigned long sel_types = 0;
+ size_t real_ff_mask_size;
+ int err;
+ int i;
+
+ if (!vdev->config->admin_cmd_exec)
+ return -EOPNOTSUPP;
+
+ cap_id_list = kzalloc(sizeof(*cap_id_list), GFP_KERNEL);
+ if (!cap_id_list)
+ return -ENOMEM;
+
+ err = virtio_admin_cap_id_list_query(vdev, cap_id_list);
+ if (err)
+ goto err_cap_list;
+
+ if (!(virtio_cap_in_list(cap_id_list, VIRTIO_NET_FF_RESOURCE_CAP) &&
+ virtio_cap_in_list(cap_id_list, VIRTIO_NET_FF_SELECTOR_CAP) &&
+ virtio_cap_in_list(cap_id_list, VIRTIO_NET_FF_ACTION_CAP))) {
+ err = -EOPNOTSUPP;
+ goto err_cap_list;
+ }
+
+ ff->ff_caps = kzalloc(sizeof(*ff->ff_caps), GFP_KERNEL);
+ if (!ff->ff_caps) {
+ err = -ENOMEM;
+ goto err_cap_list;
+ }
+
+ err = virtio_admin_cap_get(vdev,
+ VIRTIO_NET_FF_RESOURCE_CAP,
+ ff->ff_caps,
+ sizeof(*ff->ff_caps));
+
+ if (err)
+ goto err_ff;
+
+ if (!le32_to_cpu(ff->ff_caps->groups_limit) ||
+ !le32_to_cpu(ff->ff_caps->classifiers_limit) ||
+ !le32_to_cpu(ff->ff_caps->rules_limit) ||
+ !le32_to_cpu(ff->ff_caps->rules_per_group_limit) ||
+ !ff->ff_caps->selectors_per_classifier_limit) {
+ err = -EINVAL;
+ goto err_ff;
+ }
+
+ /* VIRTIO_NET_FF_MASK_TYPE start at 1 */
+ for (i = 1; i <= VIRTIO_NET_FF_MASK_TYPE_MAX; i++)
+ ff_mask_size += get_mask_size(i);
+
+ ff->ff_mask = kzalloc(ff_mask_size, GFP_KERNEL);
+ if (!ff->ff_mask) {
+ err = -ENOMEM;
+ goto err_ff;
+ }
+
+ err = virtio_admin_cap_get(vdev,
+ VIRTIO_NET_FF_SELECTOR_CAP,
+ ff->ff_mask,
+ ff_mask_size);
+
+ if (err)
+ goto err_ff_mask;
+
+ ff->ff_mask->count = min_t(u8, ff->ff_mask->count,
+ VIRTIO_NET_FF_MASK_TYPE_MAX);
+
+ ff->ff_actions = kzalloc(sizeof(*ff->ff_actions) +
+ VIRTIO_NET_FF_ACTION_MAX,
+ GFP_KERNEL);
+ if (!ff->ff_actions) {
+ err = -ENOMEM;
+ goto err_ff_mask;
+ }
+
+ err = virtio_admin_cap_get(vdev,
+ VIRTIO_NET_FF_ACTION_CAP,
+ ff->ff_actions,
+ sizeof(*ff->ff_actions) + VIRTIO_NET_FF_ACTION_MAX);
+
+ if (err)
+ goto err_ff_action;
+
+ ff->ff_actions->count = min_t(u8, ff->ff_actions->count,
+ VIRTIO_NET_FF_ACTION_MAX);
+ if (!ff->ff_actions->count)
+ goto err_ff_action;
+
+ if (le32_to_cpu(ff->ff_caps->groups_limit) < VIRTNET_FF_MAX_GROUPS) {
+ err = -ENOSPC;
+ goto err_ff_action;
+ }
+ ff->ff_caps->groups_limit = cpu_to_le32(VIRTNET_FF_MAX_GROUPS);
+
+ err = virtio_admin_cap_set(vdev,
+ VIRTIO_NET_FF_RESOURCE_CAP,
+ ff->ff_caps,
+ sizeof(*ff->ff_caps));
+ if (err)
+ goto err_ff_action;
+
+ real_ff_mask_size = sizeof(struct virtio_net_ff_cap_mask_data);
+ sel = (void *)&ff->ff_mask->selectors;
+
+ for (i = 0; i < ff->ff_mask->count; i++) {
+ /* If the selector type is unknown it may indicate the spec
+ * has been revised to include new types of selectors
+ */
+ if (!sel->type || sel->type > VIRTIO_NET_FF_MASK_TYPE_MAX)
+ break;
+
+ if (sel->length != get_mask_size(sel->type) ||
+ test_and_set_bit(sel->type, &sel_types)) {
+ err = -EPROTO;
+ goto err_ff_action;
+ }
+ real_ff_mask_size += sizeof(*sel) + sel->length;
+ if (real_ff_mask_size > ff_mask_size) {
+ err = -EPROTO;
+ goto err_ff_action;
+ }
+ sel = (void *)sel + sizeof(*sel) + sel->length;
+ }
+ ff->ff_mask->count = i;
+
+ err = virtio_admin_cap_set(vdev,
+ VIRTIO_NET_FF_SELECTOR_CAP,
+ ff->ff_mask,
+ real_ff_mask_size);
+ if (err)
+ goto err_ff_action;
+
+ err = virtio_admin_cap_set(vdev,
+ VIRTIO_NET_FF_ACTION_CAP,
+ ff->ff_actions,
+ sizeof(*ff->ff_actions) + ff->ff_actions->count);
+ if (err)
+ goto err_ff_action;
+
+ ethtool_group.group_priority = cpu_to_le16(VIRTNET_FF_ETHTOOL_GROUP_PRIORITY);
+
+ /* Use priority for the object ID. */
+ err = virtio_admin_obj_create(vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_GROUP,
+ VIRTNET_FF_ETHTOOL_GROUP_PRIORITY,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0,
+ ðtool_group,
+ sizeof(ethtool_group));
+ if (err)
+ goto err_ff_action;
+
+ xa_init_flags(&ff->classifiers, XA_FLAGS_ALLOC);
+ xa_init_flags(&ff->ethtool.rules, XA_FLAGS_ALLOC);
+ ff->vdev = vdev;
+ ff->ff_supported = true;
+
+ kfree(cap_id_list);
+
+ return 0;
+
+err_ff_action:
+ kfree(ff->ff_actions);
+ ff->ff_actions = NULL;
+err_ff_mask:
+ kfree(ff->ff_mask);
+ ff->ff_mask = NULL;
+err_ff:
+ kfree(ff->ff_caps);
+ ff->ff_caps = NULL;
+err_cap_list:
+ kfree(cap_id_list);
+
+ return err;
+}
+
+static void virtnet_ff_cleanup(struct virtnet_ff *ff)
+{
+ struct virtnet_ethtool_rule *eth_rule;
+ unsigned long i;
+
+ if (!ff->ff_supported)
+ return;
+
+ xa_for_each(&ff->ethtool.rules, i, eth_rule)
+ destroy_ethtool_rule(ff, eth_rule);
+
+ xa_destroy(&ff->ethtool.rules);
+ xa_destroy(&ff->classifiers);
+
+ virtio_admin_obj_destroy(ff->vdev,
+ VIRTIO_NET_RESOURCE_OBJ_FF_GROUP,
+ VIRTNET_FF_ETHTOOL_GROUP_PRIORITY,
+ VIRTIO_ADMIN_GROUP_TYPE_SELF,
+ 0);
+
+ kfree(ff->ff_actions);
+ kfree(ff->ff_mask);
+ kfree(ff->ff_caps);
+ ff->ff_supported = false;
+}
+
+static void virtnet_freeze_down(struct virtio_device *vdev)
+{
+ struct virtnet_info *vi = vdev->priv;
+
+ /* Make sure no work handler is accessing the device */
+ flush_work(&vi->config_work);
+ disable_rx_mode_work(vi);
+ flush_work(&vi->rx_mode_work);
+
+ if (netif_running(vi->dev)) {
+ rtnl_lock();
+ virtnet_close(vi->dev);
+ rtnl_unlock();
+ }
+
+ netif_tx_lock_bh(vi->dev);
+ netif_device_detach(vi->dev);
+ netif_tx_unlock_bh(vi->dev);
+
+ rtnl_lock();
+ virtnet_ff_cleanup(&vi->ff);
+ rtnl_unlock();
+}
+
+static int init_vqs(struct virtnet_info *vi);
+
+static int virtnet_restore_up(struct virtio_device *vdev)
+{
+ struct virtnet_info *vi = vdev->priv;
+ int err;
+
+ err = init_vqs(vi);
+ if (err)
+ return err;
+
+ err = virtnet_create_page_pools(vi);
+ if (err)
+ goto err_del_vqs;
+
+ virtio_device_ready(vdev);
+
+ enable_rx_mode_work(vi);
+
+ if (netif_running(vi->dev)) {
+ rtnl_lock();
+ err = virtnet_open(vi->dev);
+ rtnl_unlock();
+ if (err)
+ goto err_destroy_pools;
+ }
+
+ /*
+ * Initialize flow filters. Not supported is an acceptable and common
+ * return code
+ */
+ rtnl_lock();
+ err = virtnet_ff_init(&vi->ff, vi->vdev);
+ if (err && err != -EOPNOTSUPP) {
+ rtnl_unlock();
+ virtnet_freeze_down(vi->vdev);
+ remove_vq_common(vi);
+ return err;
+ }
+ rtnl_unlock();
+
+ netif_tx_lock_bh(vi->dev);
+ netif_device_attach(vi->dev);
+ netif_tx_unlock_bh(vi->dev);
+
+ return 0;
+
+err_destroy_pools:
+ virtio_reset_device(vdev);
+ free_unused_bufs(vi);
+ virtnet_destroy_page_pools(vi);
+ virtnet_del_vqs(vi);
+ return err;
+
+err_del_vqs:
+ virtio_reset_device(vdev);
+ virtnet_del_vqs(vi);
+ return err;
+}
+
+static int virtnet_set_guest_offloads(struct virtnet_info *vi, u64 offloads)
+{
+ __virtio64 *_offloads __free(kfree) = NULL;
+ struct scatterlist sg;
+
+ _offloads = kzalloc_obj(*_offloads);
+ if (!_offloads)
+ return -ENOMEM;
+
+ *_offloads = cpu_to_virtio64(vi->vdev, offloads);
+
+ sg_init_one(&sg, _offloads, sizeof(*_offloads));
+
+ if (!virtnet_send_command(vi, VIRTIO_NET_CTRL_GUEST_OFFLOADS,
+ VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, &sg)) {
+ dev_warn(&vi->dev->dev, "Fail to set guest offload.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int virtnet_clear_guest_offloads(struct virtnet_info *vi)
+{
+ u64 offloads = 0;
+
+ if (!vi->guest_offloads)
+ return 0;
+
+ return virtnet_set_guest_offloads(vi, offloads);
+}
+
+static int virtnet_restore_guest_offloads(struct virtnet_info *vi)
+{
+ u64 offloads = vi->guest_offloads;
+
+ if (!vi->guest_offloads)
+ return 0;
+
+ return virtnet_set_guest_offloads(vi, offloads);
+}
+
+static int virtnet_rq_bind_xsk_pool(struct virtnet_info *vi, struct receive_queue *rq,
+ struct xsk_buff_pool *pool)
+{
+ int err, qindex;
+
+ qindex = rq - vi->rq;
+
+ if (pool) {
+ err = xdp_rxq_info_reg(&rq->xsk_rxq_info, vi->dev, qindex, rq->napi.napi_id);
+ if (err < 0)
+ return err;
+
+ err = xdp_rxq_info_reg_mem_model(&rq->xsk_rxq_info,
+ MEM_TYPE_XSK_BUFF_POOL, NULL);
+ if (err < 0)
+ goto unreg;
+
+ xsk_pool_set_rxq_info(pool, &rq->xsk_rxq_info);
+ }
+
+ virtnet_rx_pause(vi, rq);
+
+ err = virtqueue_reset(rq->vq, virtnet_rq_unmap_free_buf, NULL);
+ if (err) {
+ netdev_err(vi->dev, "reset rx fail: rx queue index: %d err: %d\n", qindex, err);
+
+ pool = NULL;
+ }
+
+ rq->xsk_pool = pool;
+
+ virtnet_rx_resume(vi, rq, true);
+
+ if (pool)
+ return 0;
+
+unreg:
+ xdp_rxq_info_unreg(&rq->xsk_rxq_info);
+ return err;
+}
+
+static int virtnet_sq_bind_xsk_pool(struct virtnet_info *vi,
+ struct send_queue *sq,
+ struct xsk_buff_pool *pool)
+{
+ int err, qindex;
+
+ qindex = sq - vi->sq;
+
+ virtnet_tx_pause(vi, sq);
+
+ err = virtqueue_reset(sq->vq, virtnet_sq_free_unused_buf,
+ virtnet_sq_free_unused_buf_done);
+ if (err) {
+ netdev_err(vi->dev, "reset tx fail: tx queue index: %d err: %d\n", qindex, err);
+ pool = NULL;
+ }
+
+ sq->xsk_pool = pool;
+
+ virtnet_tx_resume(vi, sq);
+
+ return err;
+}
+
+static int virtnet_xsk_pool_enable(struct net_device *dev,
+ struct xsk_buff_pool *pool,
+ u16 qid)
+{
+ struct virtnet_info *vi = netdev_priv(dev);
+ struct receive_queue *rq;
+ struct device *dma_dev;
+ struct send_queue *sq;
+ dma_addr_t hdr_dma;
+ int err, size;
+
+ if (vi->hdr_len > xsk_pool_get_headroom(pool))
+ return -EINVAL;
+
+ /* In big_packets mode, xdp cannot work, so there is no need to
+ * initialize xsk of rq.
+ */
+ if (!vi->rq[qid].page_pool)
+ return -ENOENT;
+
+ if (qid >= vi->curr_queue_pairs)
+ return -EINVAL;
+
+ sq = &vi->sq[qid];
+ rq = &vi->rq[qid];
+
+ /* xsk assumes that tx and rx must have the same dma device. The af-xdp
+ * may use one buffer to receive from the rx and reuse this buffer to
+ * send by the tx. So the dma dev of sq and rq must be the same one.
+ *
+ * But vq->dma_dev allows every vq has the respective dma dev. So I
+ * check the dma dev of vq and sq is the same dev.
+ */
+ if (virtqueue_dma_dev(rq->vq) != virtqueue_dma_dev(sq->vq))
+ return -EINVAL;
dma_dev = virtqueue_dma_dev(rq->vq);
if (!dma_dev)
@@ -7043,6 +8255,15 @@ static int virtnet_probe(struct virtio_device *vdev)
virtio_device_ready(vdev);
+ /* Initialize flow filters. Not supported is an acceptable and common
+ * return code
+ */
+ err = virtnet_ff_init(&vi->ff, vi->vdev);
+ if (err && err != -EOPNOTSUPP) {
+ rtnl_unlock();
+ goto free_unregister_netdev;
+ }
+
if (vi->has_rss || vi->has_rss_hash_report) {
if (!virtnet_commit_rss_command(vi)) {
dev_warn(&vdev->dev, "RSS disabled because committing failed.\n");
@@ -7125,6 +8346,7 @@ static int virtnet_probe(struct virtio_device *vdev)
free_unregister_netdev:
unregister_netdev(dev);
+ virtnet_ff_cleanup(&vi->ff);
free_failover:
net_failover_destroy(vi->failover);
free_page_pools:
@@ -7175,6 +8397,7 @@ static void virtnet_remove(struct virtio_device *vdev)
virtnet_free_irq_moder(vi);
unregister_netdev(vi->dev);
+ virtnet_ff_cleanup(&vi->ff);
net_failover_destroy(vi->failover);
diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
index eefcfe90d6b8b..2b4a204dde331 100644
--- a/drivers/virtio/Makefile
+++ b/drivers/virtio/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_VIRTIO) += virtio.o virtio_ring.o
+obj-$(CONFIG_VIRTIO) += virtio.o virtio_ring.o virtio_admin_commands.o
obj-$(CONFIG_VIRTIO_ANCHOR) += virtio_anchor.o
obj-$(CONFIG_VIRTIO_PCI_LIB) += virtio_pci_modern_dev.o
obj-$(CONFIG_VIRTIO_PCI_LIB_LEGACY) += virtio_pci_legacy_dev.o
diff --git a/drivers/virtio/virtio_admin_commands.c b/drivers/virtio/virtio_admin_commands.c
new file mode 100644
index 0000000000000..08b41f38baea4
--- /dev/null
+++ b/drivers/virtio/virtio_admin_commands.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_admin.h>
+#include <linux/overflow.h>
+#include <uapi/linux/virtio_pci.h>
+
+int virtio_admin_cap_id_list_query(struct virtio_device *vdev,
+ struct virtio_admin_cmd_query_cap_id_result *data)
+{
+ struct virtio_admin_cmd cmd = {};
+ struct scatterlist result_sg;
+
+ if (!vdev->config->admin_cmd_exec)
+ return -EOPNOTSUPP;
+
+ sg_init_one(&result_sg, data, sizeof(*data));
+ cmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_CAP_ID_LIST_QUERY);
+ cmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SELF);
+ cmd.result_sg = &result_sg;
+
+ return vdev->config->admin_cmd_exec(vdev, &cmd);
+}
+EXPORT_SYMBOL_GPL(virtio_admin_cap_id_list_query);
+
+int virtio_admin_cap_get(struct virtio_device *vdev,
+ u16 id,
+ void *caps,
+ size_t cap_size)
+{
+ struct virtio_admin_cmd_cap_get_data *data;
+ struct virtio_admin_cmd cmd = {};
+ struct scatterlist result_sg;
+ struct scatterlist data_sg;
+ int err;
+
+ if (!vdev->config->admin_cmd_exec)
+ return -EOPNOTSUPP;
+
+ data = kzalloc_obj(*data);
+ if (!data)
+ return -ENOMEM;
+
+ data->id = cpu_to_le16(id);
+ sg_init_one(&data_sg, data, sizeof(*data));
+ sg_init_one(&result_sg, caps, cap_size);
+ cmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_DEVICE_CAP_GET);
+ cmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SELF);
+ cmd.data_sg = &data_sg;
+ cmd.result_sg = &result_sg;
+
+ err = vdev->config->admin_cmd_exec(vdev, &cmd);
+ kfree(data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(virtio_admin_cap_get);
+
+int virtio_admin_cap_set(struct virtio_device *vdev,
+ u16 id,
+ const void *caps,
+ size_t cap_size)
+{
+ struct virtio_admin_cmd_cap_set_data *data;
+ struct virtio_admin_cmd cmd = {};
+ struct scatterlist data_sg;
+ size_t data_size;
+ int err;
+
+ if (!vdev->config->admin_cmd_exec)
+ return -EOPNOTSUPP;
+
+ if (check_add_overflow(sizeof(*data), cap_size, &data_size))
+ return -EOVERFLOW;
+
+ data = kzalloc(data_size, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->id = cpu_to_le16(id);
+ memcpy(data->cap_specific_data, caps, cap_size);
+ sg_init_one(&data_sg, data, data_size);
+ cmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_DRIVER_CAP_SET);
+ cmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SELF);
+ cmd.data_sg = &data_sg;
+ cmd.result_sg = NULL;
+
+ err = vdev->config->admin_cmd_exec(vdev, &cmd);
+ kfree(data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(virtio_admin_cap_set);
+
+int virtio_admin_obj_create(struct virtio_device *vdev,
+ u16 obj_type,
+ u32 obj_id,
+ u16 group_type,
+ u64 group_member_id,
+ const void *obj_specific_data,
+ size_t obj_specific_data_size)
+{
+ size_t data_size = sizeof(struct virtio_admin_cmd_resource_obj_create_data);
+ struct virtio_admin_cmd_resource_obj_create_data *obj_create_data;
+ struct virtio_admin_cmd cmd = {};
+ struct scatterlist data_sg;
+ void *data;
+ int err;
+
+ if (!vdev->config->admin_cmd_exec)
+ return -EOPNOTSUPP;
+
+ if (check_add_overflow(data_size, obj_specific_data_size, &data_size))
+ return -EOVERFLOW;
+
+ data = kzalloc(data_size, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ obj_create_data = data;
+ obj_create_data->hdr.type = cpu_to_le16(obj_type);
+ obj_create_data->hdr.id = cpu_to_le32(obj_id);
+ memcpy(obj_create_data->resource_obj_specific_data, obj_specific_data,
+ obj_specific_data_size);
+ sg_init_one(&data_sg, data, data_size);
+
+ cmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_RESOURCE_OBJ_CREATE);
+ cmd.group_type = cpu_to_le16(group_type);
+ cmd.group_member_id = cpu_to_le64(group_member_id);
+ cmd.data_sg = &data_sg;
+
+ err = vdev->config->admin_cmd_exec(vdev, &cmd);
+ kfree(data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(virtio_admin_obj_create);
+
+void virtio_admin_obj_destroy(struct virtio_device *vdev,
+ u16 obj_type,
+ u32 obj_id,
+ u16 group_type,
+ u64 group_member_id)
+{
+ struct virtio_admin_cmd_resource_obj_cmd_hdr *data;
+ struct virtio_admin_cmd cmd = {};
+ struct scatterlist data_sg;
+ int err;
+
+ if (!vdev->config->admin_cmd_exec)
+ return;
+
+ data = kzalloc_obj(*data);
+ if (WARN_ON(!data))
+ return;
+
+ data->type = cpu_to_le16(obj_type);
+ data->id = cpu_to_le32(obj_id);
+ sg_init_one(&data_sg, data, sizeof(*data));
+ cmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_RESOURCE_OBJ_DESTROY);
+ cmd.group_type = cpu_to_le16(group_type);
+ cmd.group_member_id = cpu_to_le64(group_member_id);
+ cmd.data_sg = &data_sg;
+
+ err = vdev->config->admin_cmd_exec(vdev, &cmd);
+ kfree(data);
+
+ WARN_ON_ONCE(err);
+}
+EXPORT_SYMBOL_GPL(virtio_admin_obj_destroy);
diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c
index 164f480b18a6f..ec0c92c782d6e 100644
--- a/drivers/virtio/virtio_pci_common.c
+++ b/drivers/virtio/virtio_pci_common.c
@@ -256,6 +256,11 @@ static void vp_del_vq(struct virtqueue *vq, struct virtio_pci_vq_info *info)
spin_unlock_irqrestore(&vp_dev->lock, flags);
}
+ if (vp_is_avq(vq->vdev, vq->index)) {
+ cancel_work_sync(&vp_dev->admin_vq.work);
+ vp_dev->admin_vq.info = NULL;
+ }
+
vp_dev->del_vq(info);
kfree(info);
}
diff --git a/drivers/virtio/virtio_pci_common.h b/drivers/virtio/virtio_pci_common.h
index 8cd01de27bafe..8b0178295342f 100644
--- a/drivers/virtio/virtio_pci_common.h
+++ b/drivers/virtio/virtio_pci_common.h
@@ -30,6 +30,7 @@
#include <linux/highmem.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
+#include <linux/workqueue.h>
struct virtio_pci_vq_info {
/* the actual virtqueue */
@@ -46,9 +47,10 @@ struct virtio_pci_admin_vq {
/* Virtqueue info associated with this admin queue. */
struct virtio_pci_vq_info *info;
/* Protects virtqueue access. */
- spinlock_t lock;
+ struct mutex lock;
+ /* Admin command completion work. */
+ struct work_struct work;
u64 supported_cmds;
- u64 supported_caps;
u8 max_dev_parts_objects;
struct ida dev_parts_ida;
/* Name of the admin queue: avq.$vq_index. */
diff --git a/drivers/virtio/virtio_pci_modern.c b/drivers/virtio/virtio_pci_modern.c
index 6d8ae2a6a8caf..3b2515e29728a 100644
--- a/drivers/virtio/virtio_pci_modern.c
+++ b/drivers/virtio/virtio_pci_modern.c
@@ -47,13 +47,20 @@ static int vp_avq_index(struct virtio_device *vdev, u16 *index, u16 *num)
void vp_modern_avq_done(struct virtqueue *vq)
{
struct virtio_pci_device *vp_dev = to_vp_device(vq->vdev);
- struct virtio_pci_admin_vq *admin_vq = &vp_dev->admin_vq;
+
+ schedule_work(&vp_dev->admin_vq.work);
+}
+
+static void vp_modern_avq_work(struct work_struct *work)
+{
unsigned int status_size = sizeof(struct virtio_admin_cmd_status);
+ struct virtio_pci_admin_vq *admin_vq =
+ container_of(work, struct virtio_pci_admin_vq, work);
+ struct virtqueue *vq = admin_vq->info->vq;
struct virtio_admin_cmd *cmd;
- unsigned long flags;
unsigned int len;
- spin_lock_irqsave(&admin_vq->lock, flags);
+ mutex_lock(&admin_vq->lock);
do {
virtqueue_disable_cb(vq);
while ((cmd = virtqueue_get_buf(vq, &len))) {
@@ -71,7 +78,7 @@ void vp_modern_avq_done(struct virtqueue *vq)
complete(&cmd->completion);
}
} while (!virtqueue_enable_cb(vq));
- spin_unlock_irqrestore(&admin_vq->lock, flags);
+ mutex_unlock(&admin_vq->lock);
}
static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,
@@ -82,7 +89,6 @@ static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,
struct virtio_admin_cmd *cmd)
{
struct virtqueue *vq;
- unsigned long flags;
int ret;
vq = admin_vq->info->vq;
@@ -100,11 +106,11 @@ static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,
if (virtqueue_is_broken(vq))
return -EIO;
- spin_lock_irqsave(&admin_vq->lock, flags);
+ mutex_lock(&admin_vq->lock);
ret = virtqueue_add_sgs(vq, sgs, out_num, in_num, cmd, GFP_KERNEL);
if (ret < 0) {
if (ret == -ENOSPC) {
- spin_unlock_irqrestore(&admin_vq->lock, flags);
+ mutex_unlock(&admin_vq->lock);
cpu_relax();
goto again;
}
@@ -112,14 +118,14 @@ static int virtqueue_exec_admin_cmd(struct virtio_pci_admin_vq *admin_vq,
}
if (!virtqueue_kick(vq))
goto unlock_err;
- spin_unlock_irqrestore(&admin_vq->lock, flags);
+ mutex_unlock(&admin_vq->lock);
wait_for_completion(&cmd->completion);
return cmd->ret;
unlock_err:
- spin_unlock_irqrestore(&admin_vq->lock, flags);
+ mutex_unlock(&admin_vq->lock);
return -EIO;
}
@@ -304,10 +310,10 @@ virtio_pci_admin_cmd_dev_parts_objects_enable(struct virtio_device *virtio_dev)
static void virtio_pci_admin_cmd_cap_init(struct virtio_device *virtio_dev)
{
- struct virtio_pci_device *vp_dev = to_vp_device(virtio_dev);
struct virtio_admin_cmd_query_cap_id_result *data;
struct virtio_admin_cmd cmd = {};
struct scatterlist result_sg;
+ u64 caps;
int ret;
data = kzalloc_obj(*data);
@@ -323,12 +329,8 @@ static void virtio_pci_admin_cmd_cap_init(struct virtio_device *virtio_dev)
if (ret)
goto end;
- /* Max number of caps fits into a single u64 */
- BUILD_BUG_ON(sizeof(data->supported_caps) > sizeof(u64));
-
- vp_dev->admin_vq.supported_caps = le64_to_cpu(data->supported_caps[0]);
-
- if (!(vp_dev->admin_vq.supported_caps & (1 << VIRTIO_DEV_PARTS_CAP)))
+ caps = le64_to_cpu(data->supported_caps[0]);
+ if (!(caps & BIT_ULL(VIRTIO_DEV_PARTS_CAP)))
goto end;
virtio_pci_admin_cmd_dev_parts_objects_enable(virtio_dev);
@@ -354,6 +356,8 @@ static void vp_modern_avq_cleanup(struct virtio_device *vdev)
if (!virtio_has_feature(vdev, VIRTIO_F_ADMIN_VQ))
return;
+ cancel_work_sync(&vp_dev->admin_vq.work);
+
vq = vp_dev->admin_vq.info->vq;
if (!vq)
return;
@@ -558,10 +562,12 @@ static void vp_reset(struct virtio_device *vdev)
while (vp_modern_get_status(mdev))
msleep(1);
- vp_modern_avq_cleanup(vdev);
-
- /* Flush pending VQ/configuration callbacks. */
+ /* Flush pending VQ/configuration callbacks before cleanup, so that
+ * vp_modern_avq_done() can no longer schedule admin_vq.work.
+ */
vp_synchronize_vectors(vdev);
+
+ vp_modern_avq_cleanup(vdev);
}
static int vp_active_vq(struct virtqueue *vq, u16 msix_vec)
@@ -1242,6 +1248,7 @@ static const struct virtio_config_ops virtio_pci_config_nodev_ops = {
.get_shm_region = vp_get_shm_region,
.disable_vq_and_reset = vp_modern_disable_vq_and_reset,
.enable_vq_after_reset = vp_modern_enable_vq_after_reset,
+ .admin_cmd_exec = vp_modern_admin_cmd_exec,
};
static const struct virtio_config_ops virtio_pci_config_ops = {
@@ -1262,6 +1269,7 @@ static const struct virtio_config_ops virtio_pci_config_ops = {
.get_shm_region = vp_get_shm_region,
.disable_vq_and_reset = vp_modern_disable_vq_and_reset,
.enable_vq_after_reset = vp_modern_enable_vq_after_reset,
+ .admin_cmd_exec = vp_modern_admin_cmd_exec,
};
/* the PCI probing function */
@@ -1288,8 +1296,9 @@ int virtio_pci_modern_probe(struct virtio_pci_device *vp_dev)
vp_dev->avq_index = vp_avq_index;
vp_dev->isr = mdev->isr;
vp_dev->vdev.id = mdev->id;
+ mutex_init(&vp_dev->admin_vq.lock);
+ INIT_WORK(&vp_dev->admin_vq.work, vp_modern_avq_work);
- spin_lock_init(&vp_dev->admin_vq.lock);
return 0;
}
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 12683b5d125e4..adecb9225ee3c 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -1567,4 +1567,10 @@ struct ethtool_forced_speed_map {
void
ethtool_forced_speed_maps_init(struct ethtool_forced_speed_map *maps, u32 size);
+
+static inline u32 ethtool_flow_type_mask(u32 flow_type)
+{
+ return flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS);
+}
+
#endif /* _LINUX_ETHTOOL_H */
diff --git a/include/linux/virtio_admin.h b/include/linux/virtio_admin.h
new file mode 100644
index 0000000000000..9095a04814956
--- /dev/null
+++ b/include/linux/virtio_admin.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Header file for virtio admin operations
+ */
+
+#ifndef _LINUX_VIRTIO_ADMIN_H
+#define _LINUX_VIRTIO_ADMIN_H
+
+#include <linux/bug.h>
+#include <linux/types.h>
+#include <linux/byteorder/generic.h>
+#include <uapi/linux/virtio_pci.h>
+
+struct virtio_device;
+
+/**
+ * virtio_cap_in_list - Check if a capability is supported in the capability list
+ * @cap_list: Pointer to capability list structure containing supported_caps array
+ * @cap: Capability ID to check
+ *
+ * The cap_list contains a supported_caps array of little-endian 64-bit integers
+ * where each bit represents a capability. Bit 0 of the first element represents
+ * capability ID 0, bit 1 represents capability ID 1, and so on.
+ *
+ * Return: true if capability is supported, false otherwise
+ */
+static inline bool virtio_cap_in_list(
+ const struct virtio_admin_cmd_query_cap_id_result *cap_list, u16 cap)
+{
+ BUILD_BUG_ON(cap > VIRTIO_ADMIN_MAX_CAP);
+ return !!(1 & (le64_to_cpu(cap_list->supported_caps[cap / 64]) >>
+ (cap % 64)));
+}
+
+/**
+ * virtio_admin_cap_id_list_query - Query the list of available capability IDs
+ * @vdev: The virtio device to query
+ * @data: Pointer to result structure (must be zero-initialized and heap allocated)
+ *
+ * This function queries the virtio device for the list of available capability
+ * IDs that can be used with virtio_admin_cap_get() and virtio_admin_cap_set().
+ * The result is stored in the provided data structure.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin
+ * operations or capability queries, or a negative error code on other failures.
+ */
+int virtio_admin_cap_id_list_query(struct virtio_device *vdev,
+ struct virtio_admin_cmd_query_cap_id_result *data);
+
+/**
+ * virtio_admin_cap_get - Get capability data for a specific capability ID
+ * @vdev: The virtio device
+ * @id: Capability ID to retrieve
+ * @caps: Pointer to capability data structure (must be heap allocated)
+ * @cap_size: Size of the capability data structure
+ *
+ * This function retrieves a specific capability from the virtio device.
+ * The capability data is stored in the provided buffer. The caller must
+ * ensure the buffer is large enough to hold the capability data.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin
+ * operations or capability retrieval, or a negative error code on other failures.
+ */
+int virtio_admin_cap_get(struct virtio_device *vdev,
+ u16 id,
+ void *caps,
+ size_t cap_size);
+
+/**
+ * virtio_admin_cap_set - Set capability data for a specific capability ID
+ * @vdev: The virtio device
+ * @id: Capability ID to set
+ * @caps: Pointer to capability data structure (must be heap allocated)
+ * @cap_size: Size of the capability data structure
+ *
+ * This function sets a specific capability on the virtio device.
+ * The capability data is read from the provided buffer and applied
+ * to the device. The device may validate the capability data before
+ * applying it.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin
+ * operations or capability setting, or a negative error code on other failures.
+ */
+int virtio_admin_cap_set(struct virtio_device *vdev,
+ u16 id,
+ const void *caps,
+ size_t cap_size);
+
+/**
+ * virtio_admin_obj_create - Create an object on a virtio device
+ * @vdev: the virtio device
+ * @obj_type: type of object to create
+ * @obj_id: ID for the new object
+ * @group_type: administrative group type for the operation
+ * @group_member_id: member identifier within the administrative group
+ * @obj_specific_data: object-specific data for creation
+ * @obj_specific_data_size: size of the object-specific data in bytes
+ *
+ * Creates a new object on the virtio device with the specified type and ID.
+ * The object may require object-specific data for proper initialization.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device doesn't support admin
+ * operations or object creation, or a negative error code on other failures.
+ */
+int virtio_admin_obj_create(struct virtio_device *vdev,
+ u16 obj_type,
+ u32 obj_id,
+ u16 group_type,
+ u64 group_member_id,
+ const void *obj_specific_data,
+ size_t obj_specific_data_size);
+
+/**
+ * virtio_admin_obj_destroy - Destroy an object on a virtio device
+ * @vdev: the virtio device
+ * @obj_type: type of object to destroy
+ * @obj_id: ID of the object to destroy
+ * @group_type: administrative group type for the operation
+ * @group_member_id: member identifier within the administrative group
+ *
+ * Destroys an existing object on the virtio device with the specified type
+ * and ID.
+ */
+void virtio_admin_obj_destroy(struct virtio_device *vdev,
+ u16 obj_type,
+ u32 obj_id,
+ u16 group_type,
+ u64 group_member_id);
+
+#endif /* _LINUX_VIRTIO_ADMIN_H */
diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h
index 69f84ea85d71a..e36a32e0a20c8 100644
--- a/include/linux/virtio_config.h
+++ b/include/linux/virtio_config.h
@@ -108,6 +108,10 @@ struct virtqueue_info {
* Returns 0 on success or error status
* If disable_vq_and_reset is set, then enable_vq_after_reset must also be
* set.
+ * @admin_cmd_exec: Execute an admin VQ command (optional).
+ * vdev: the virtio_device
+ * cmd: the command to execute
+ * Returns 0 on success or error status
*/
struct virtio_config_ops {
void (*get)(struct virtio_device *vdev, unsigned offset,
@@ -137,6 +141,8 @@ struct virtio_config_ops {
struct virtio_shm_region *region, u8 id);
int (*disable_vq_and_reset)(struct virtqueue *vq);
int (*enable_vq_after_reset)(struct virtqueue *vq);
+ int (*admin_cmd_exec)(struct virtio_device *vdev,
+ struct virtio_admin_cmd *cmd);
};
/**
diff --git a/include/uapi/linux/virtio_net_ff.h b/include/uapi/linux/virtio_net_ff.h
new file mode 100644
index 0000000000000..9152021c99baa
--- /dev/null
+++ b/include/uapi/linux/virtio_net_ff.h
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+ *
+ * Header file for virtio_net flow filters
+ */
+#ifndef _LINUX_VIRTIO_NET_FF_H
+#define _LINUX_VIRTIO_NET_FF_H
+
+#include <linux/types.h>
+#include <linux/stddef.h>
+
+#define VIRTIO_NET_FF_RESOURCE_CAP 0x800
+#define VIRTIO_NET_FF_SELECTOR_CAP 0x801
+#define VIRTIO_NET_FF_ACTION_CAP 0x802
+
+#define VIRTIO_NET_RESOURCE_OBJ_FF_GROUP 0x0200
+#define VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER 0x0201
+#define VIRTIO_NET_RESOURCE_OBJ_FF_RULE 0x0202
+
+/**
+ * struct virtio_net_ff_cap_data - Flow filter resource capability limits
+ * @groups_limit: maximum number of flow filter groups supported by the device
+ * @classifiers_limit: maximum number of classifiers supported by the device
+ * @rules_limit: maximum number of rules supported device-wide across all groups
+ * @rules_per_group_limit: maximum number of rules allowed in a single group
+ * @last_rule_priority: priority value associated with the lowest-priority rule
+ * @selectors_per_classifier_limit: maximum selectors allowed in one classifier
+ */
+struct virtio_net_ff_cap_data {
+ __le32 groups_limit;
+ __le32 classifiers_limit;
+ __le32 rules_limit;
+ __le32 rules_per_group_limit;
+ __u8 last_rule_priority;
+ __u8 selectors_per_classifier_limit;
+ /* private: */
+ __u8 reserved[2];
+};
+
+/**
+ * struct virtio_net_ff_selector - Selector mask descriptor
+ * @type: selector type, one of VIRTIO_NET_FF_MASK_TYPE_* constants
+ * @flags: selector flags, see VIRTIO_NET_FF_MASK_F_* constants
+ * @reserved: must be set to 0 by the driver and ignored by the device
+ * @length: size in bytes of @mask
+ * @reserved1: must be set to 0 by the driver and ignored by the device
+ * @mask: variable-length mask payload for @type, length given by @length
+ *
+ * A selector describes a header mask that a classifier can apply. The format
+ * of @mask depends on @type.
+ */
+struct virtio_net_ff_selector {
+ __u8 type;
+ __u8 flags;
+ __u8 reserved[2];
+ __u8 length;
+ __u8 reserved1[3];
+ __u8 mask[] __counted_by(length);
+};
+
+#define VIRTIO_NET_FF_MASK_TYPE_ETH 1
+#define VIRTIO_NET_FF_MASK_TYPE_IPV4 2
+#define VIRTIO_NET_FF_MASK_TYPE_IPV6 3
+#define VIRTIO_NET_FF_MASK_TYPE_TCP 4
+#define VIRTIO_NET_FF_MASK_TYPE_UDP 5
+#define VIRTIO_NET_FF_MASK_TYPE_MAX VIRTIO_NET_FF_MASK_TYPE_UDP
+
+/**
+ * struct virtio_net_ff_cap_mask_data - Supported selector mask formats
+ * @count: number of entries in @selectors
+ * @reserved: must be set to 0 by the driver and ignored by the device
+ * @selectors: packed array of struct virtio_net_ff_selector.
+ */
+struct virtio_net_ff_cap_mask_data {
+ __u8 count;
+ __u8 reserved[7];
+ __u8 selectors[];
+};
+
+#define VIRTIO_NET_FF_MASK_F_PARTIAL_MASK (1 << 0)
+
+#define VIRTIO_NET_FF_ACTION_DROP 1
+#define VIRTIO_NET_FF_ACTION_RX_VQ 2
+#define VIRTIO_NET_FF_ACTION_MAX VIRTIO_NET_FF_ACTION_RX_VQ
+/**
+ * struct virtio_net_ff_actions - Supported flow actions
+ * @count: number of supported actions in @actions
+ * @reserved: must be set to 0 by the driver and ignored by the device
+ * @actions: array of action identifiers (VIRTIO_NET_FF_ACTION_*)
+ */
+struct virtio_net_ff_actions {
+ __u8 count;
+ __u8 reserved[7];
+ __u8 actions[] __counted_by(count);
+};
+
+/**
+ * struct virtio_net_resource_obj_ff_group - Flow filter group object
+ * @group_priority: priority of the group used to order evaluation
+ *
+ * This structure is the payload for the VIRTIO_NET_RESOURCE_OBJ_FF_GROUP
+ * administrative object. Devices use @group_priority to order flow filter
+ * groups. Multi-byte fields are little-endian.
+ */
+struct virtio_net_resource_obj_ff_group {
+ __le16 group_priority;
+};
+
+/**
+ * struct virtio_net_resource_obj_ff_classifier - Flow filter classifier object
+ * @count: number of selector entries in @selectors
+ * @reserved: must be set to 0 by the driver and ignored by the device
+ * @selectors: array of selector descriptors that define match masks
+ *
+ * Payload for the VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER administrative object.
+ * Each selector describes a header mask used to match packets
+ * (see struct virtio_net_ff_selector). Selectors appear in the order they are
+ * to be applied.
+ */
+struct virtio_net_resource_obj_ff_classifier {
+ __u8 count;
+ __u8 reserved[7];
+ __u8 selectors[];
+};
+
+/**
+ * struct virtio_net_resource_obj_ff_rule - Flow filter rule object
+ * @group_id: identifier of the target flow filter group
+ * @classifier_id: identifier of the classifier referenced by this rule
+ * @rule_priority: relative priority of this rule within the group
+ * @key_length: number of bytes in @keys
+ * @action: action to perform, one of VIRTIO_NET_FF_ACTION_*
+ * @reserved: must be set to 0 by the driver and ignored by the device
+ * @vq_index: RX virtqueue index for VIRTIO_NET_FF_ACTION_RX_VQ, 0 otherwise
+ * @reserved1: must be set to 0 by the driver and ignored by the device
+ * @keys: concatenated key bytes matching the classifier's selectors order
+ *
+ * Payload for the VIRTIO_NET_RESOURCE_OBJ_FF_RULE administrative object.
+ * @group_id and @classifier_id refer to previously created objects of types
+ * VIRTIO_NET_RESOURCE_OBJ_FF_GROUP and VIRTIO_NET_RESOURCE_OBJ_FF_CLASSIFIER
+ * respectively. The key bytes are compared against packet headers using the
+ * masks provided by the classifier's selectors. Multi-byte fields are
+ * little-endian.
+ */
+struct virtio_net_resource_obj_ff_rule {
+ __le32 group_id;
+ __le32 classifier_id;
+ __u8 rule_priority;
+ __u8 key_length; /* length of key in bytes */
+ __u8 action;
+ __u8 reserved;
+ __le16 vq_index;
+ __u8 reserved1[2];
+ __u8 keys[];
+};
+
+#endif
diff --git a/include/uapi/linux/virtio_pci.h b/include/uapi/linux/virtio_pci.h
index e732e3456e275..96d097d3757e4 100644
--- a/include/uapi/linux/virtio_pci.h
+++ b/include/uapi/linux/virtio_pci.h
@@ -315,15 +315,17 @@ struct virtio_admin_cmd_notify_info_result {
#define VIRTIO_DEV_PARTS_CAP 0x0000
+#define VIRTIO_ADMIN_MAX_CAP 0x0fff
+
struct virtio_dev_parts_cap {
__u8 get_parts_resource_objects_limit;
__u8 set_parts_resource_objects_limit;
};
-#define MAX_CAP_ID __KERNEL_DIV_ROUND_UP(VIRTIO_DEV_PARTS_CAP + 1, 64)
+#define VIRTIO_ADMIN_CAP_ID_ARRAY_SIZE __KERNEL_DIV_ROUND_UP(VIRTIO_ADMIN_MAX_CAP + 1, 64)
struct virtio_admin_cmd_query_cap_id_result {
- __le64 supported_caps[MAX_CAP_ID];
+ __le64 supported_caps[VIRTIO_ADMIN_CAP_ID_ARRAY_SIZE];
};
struct virtio_admin_cmd_cap_get_data {
|
| 13/3 | 2026/08/16 12:48 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33225 output: 390 thoughts: 4244 Thoughts: **Evaluation of Virtio-Net Flow Filter Patch for KMSAN Fuzzing**
Alright, let's break this down. The task is to determine if this virtio-net flow filter patch warrants a dedicated KMSAN fuzzing session. My focus is on potential uninitialized memory reads or information leaks, as this is the primary area KMSAN excels in.
My initial assessment is focused on the new data structures and functions introduced for flow filter support using ethtool rxnfc. The first thing I did was to thoroughly check all allocations for potential vulnerabilities. I paid particular attention to allocations of `key`, `c` (classifier), `ff_rule`, `data` (in `virtio_admin_cap_get`, `virtio_admin_cap_set`, `virtio_admin_obj_create`, `virtio_admin_obj_destroy`), and other related structures like `ethtool_group`, `cap_id_list`, `ff->ff_caps`, `ff->ff_mask`, `ff->ff_actions`, `eth_rule`, and `_offloads`. Every single one of these allocations uses `kzalloc` or `kzalloc_obj`, which guarantees zero-initialization. This is a very good first sign, and it removes a major worry.
Next, I looked into the functions that set up the key and mask for flow filters, such as `setup_eth_hdr_key_mask`, `setup_ip_key_mask`, and `setup_transport_key_mask`. Here, the focus was to see if any uninitialized memory could be sent to the device, but the allocations of `selector` and `key` in `setup_eth_hdr_key_mask` are also `kzalloc`'ed.
I also checked functions that deal with the admin commands like `virtio_admin_cmd_cap_init`, `virtio_admin_cap_id_list_query`, `virtio_admin_cap_get`, `virtio_admin_cap_set`, `virtio_admin_obj_create` and `virtio_admin_obj_destroy`. Critically, the `struct virtio_admin_cmd` is always initialized with `{}` and data sent to the device via these functions appears to be fully zeroed.
I then considered what data is copied back to user space. `virtnet_get_rxnfc`, which is called by the ethtool core, copies `info` struct and, ultimately, the user-provided `flow_spec`. Crucially, the code copies from a `kzalloc`ed `eth_rule` which then copies from the ethtool user structure. There's no scenario where uninitialized kernel memory is leaked to user space, so no issue here.
Since all allocations are zeroed and there's no way for uninitialized kernel memory to be copied back to user space or sent to the device, I've concluded there is no risk of uninitialized memory usage or information leaks in this patch series. KASAN should be sufficient for the other types of bugs (out-of-bounds, use-after-free, etc.). Therefore, I'm marking this patch as *not* needing a dedicated KMSAN session.
|
| 14/3 | 2026/08/16 12:49 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series introduces flow filter support (ethtool rxnfc) for virtio-net. It adds new structures and functions to handle virtio admin commands and flow filter rules. \n\nA careful review of the memory allocations and data structures introduced by this patch shows that all new memory allocations for keys, masks, classifiers, rules, and admin command data are performed using `kzalloc` or `kzalloc_obj`, ensuring they are fully zero-initialized. For example:\n- `key = kzalloc(key_size, GFP_KERNEL);`\n- `c = kzalloc(classifier_size + ..., GFP_KERNEL);`\n- `ff_rule = kzalloc(sizeof(*ff_rule) + key_size, GFP_KERNEL);`\n- `data = kzalloc_obj(*data);` in admin command functions.\n\nFurthermore, local structures like `struct virtio_admin_cmd cmd = {};` and `struct virtio_net_resource_obj_ff_group ethtool_group = {};` are explicitly zero-initialized.\n\nWhen copying data back to user space via ethtool (`virtnet_ethtool_get_flow`, `virtnet_ethtool_get_all_flows`), the code only copies back the `flow_spec` that was previously provided by the user and stored in the fully zeroed `eth_rule` structure.\n\nSince all memory introduced or modified by this patch is properly initialized before being used, sent to the device, or copied to user space, there is no risk of uninitialized memory reads or info-leaks. Standard KASAN is sufficient to catch any potential out-of-bounds accesses or use-after-free bugs in this code.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series introduces flow filter support (ethtool rxnfc) for virtio-net. It adds new structures and functions to handle virtio admin commands and flow filter rules. \n\nA careful review of the memory allocations and data structures introduced by this patch shows that all new memory allocations for keys, masks, classifiers, rules, and admin command data are performed using `kzalloc` or `kzalloc_obj`, ensuring they are fully zero-initialized. For example:\n- `key = kzalloc(key_size, GFP_KERNEL);`\n- `c = kzalloc(classifier_size + ..., GFP_KERNEL);`\n- `ff_rule = kzalloc(sizeof(*ff_rule) + key_size, GFP_KERNEL);`\n- `data = kzalloc_obj(*data);` in admin command functions.\n\nFurthermore, local structures like `struct virtio_admin_cmd cmd = {};` and `struct virtio_net_resource_obj_ff_group ethtool_group = {};` are explicitly zero-initialized.\n\nWhen copying data back to user space via ethtool (`virtnet_ethtool_get_flow`, `virtnet_ethtool_get_all_flows`), the code only copies back the `flow_spec` that was previously provided by the user and stored in the fully zeroed `eth_rule` structure.\n\nSince all memory introduced or modified by this patch is properly initialized before being used, sent to the device, or copied to user space, there is no risk of uninitialized memory reads or info-leaks. Standard KASAN is sufficient to catch any potential out-of-bounds accesses or use-after-free bugs in this code.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|