From 31557b3487b349464daf42bc4366153743c1e727 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 9 Jun 2025 07:39:33 -0700 Subject: uapi: in6: restore visibility of most IPv6 socket options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A decade ago commit 6d08acd2d32e ("in6: fix conflict with glibc") hid the definitions of IPV6 options, because GCC was complaining about duplicates. The commit did not list the warnings seen, but trying to recreate them now I think they are (building iproute2): In file included from ./include/uapi/rdma/rdma_user_cm.h:39, from rdma.h:16, from res.h:9, from res-ctx.c:7: ../include/uapi/linux/in6.h:171:9: warning: ‘IPV6_ADD_MEMBERSHIP’ redefined 171 | #define IPV6_ADD_MEMBERSHIP 20 | ^~~~~~~~~~~~~~~~~~~ In file included from /usr/include/netinet/in.h:37, from rdma.h:13: /usr/include/bits/in.h:233:10: note: this is the location of the previous definition 233 | # define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP | ^~~~~~~~~~~~~~~~~~~ ../include/uapi/linux/in6.h:172:9: warning: ‘IPV6_DROP_MEMBERSHIP’ redefined 172 | #define IPV6_DROP_MEMBERSHIP 21 | ^~~~~~~~~~~~~~~~~~~~ /usr/include/bits/in.h:234:10: note: this is the location of the previous definition 234 | # define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP | ^~~~~~~~~~~~~~~~~~~~ Compilers don't complain about redefinition if the defines are identical, but here we have the kernel using the literal value, and glibc using an indirection (defining to a name of another define, with the same numerical value). Problem is, the commit in question hid all the IPV6 socket options, and glibc has a pretty sparse list. For instance it lacks Flow Label related options. Willem called this out in commit 3fb321fde22d ("selftests/net: ipv6 flowlabel"): /* uapi/glibc weirdness may leave this undefined */ #ifndef IPV6_FLOWINFO #define IPV6_FLOWINFO 11 #endif More interestingly some applications (socat) use a #ifdef IPV6_FLOWINFO to gate compilation of thier rudimentary flow label support. (For added confusion socat misspells it as IPV4_FLOWINFO in some places.) Hide only the two defines we know glibc has a problem with. If we discover more warnings we can hide more but we should avoid covering the entire block of defines for "IPV6 socket options". Link: https://patch.msgid.link/20250609143933.1654417-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/uapi/linux/in6.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h index ff8d21f9e95b..5a47339ef7d7 100644 --- a/include/uapi/linux/in6.h +++ b/include/uapi/linux/in6.h @@ -152,7 +152,6 @@ struct in6_flowlabel_req { /* * IPV6 socket options */ -#if __UAPI_DEF_IPV6_OPTIONS #define IPV6_ADDRFORM 1 #define IPV6_2292PKTINFO 2 #define IPV6_2292HOPOPTS 3 @@ -169,8 +168,10 @@ struct in6_flowlabel_req { #define IPV6_MULTICAST_IF 17 #define IPV6_MULTICAST_HOPS 18 #define IPV6_MULTICAST_LOOP 19 +#if __UAPI_DEF_IPV6_OPTIONS #define IPV6_ADD_MEMBERSHIP 20 #define IPV6_DROP_MEMBERSHIP 21 +#endif #define IPV6_ROUTER_ALERT 22 #define IPV6_MTU_DISCOVER 23 #define IPV6_MTU 24 @@ -203,7 +204,6 @@ struct in6_flowlabel_req { #define IPV6_IPSEC_POLICY 34 #define IPV6_XFRM_POLICY 35 #define IPV6_HDRINCL 36 -#endif /* * Multicast: -- cgit v1.2.3 From c035e736038045b411cb368e63f07bc2f5dbc0e1 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kubalewski Date: Thu, 12 Jun 2025 17:28:33 +0200 Subject: dpll: add phase-offset-monitor feature to netlink spec Add enum dpll_feature_state for control over features. Add dpll device level attribute: DPLL_A_PHASE_OFFSET_MONITOR - to allow control over a phase offset monitor feature. Attribute is present and shall return current state of a feature (enum dpll_feature_state), if the device driver provides such capability, otherwie attribute shall not be present. Reviewed-by: Aleksandr Loktionov Reviewed-by: Milena Olech Reviewed-by: Jiri Pirko Signed-off-by: Arkadiusz Kubalewski Acked-by: Vadim Fedorenko Link: https://patch.msgid.link/20250612152835.1703397-2-arkadiusz.kubalewski@intel.com Signed-off-by: Jakub Kicinski --- Documentation/driver-api/dpll.rst | 18 ++++++++++++++++++ Documentation/netlink/specs/dpll.yaml | 24 ++++++++++++++++++++++++ drivers/dpll/dpll_nl.c | 5 +++-- include/uapi/linux/dpll.h | 12 ++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index e6855cd37e85..195e1e5d9a58 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -214,6 +214,24 @@ offset values are fractional with 3-digit decimal places and shell be divided with ``DPLL_PIN_PHASE_OFFSET_DIVIDER`` to get integer part and modulo divided to get fractional part. +Phase offset monitor +==================== + +Phase offset measurement is typically performed against the current active +source. However, some DPLL (Digital Phase-Locked Loop) devices may offer +the capability to monitor phase offsets across all available inputs. +The attribute and current feature state shall be included in the response +message of the ``DPLL_CMD_DEVICE_GET`` command for supported DPLL devices. +In such cases, users can also control the feature using the +``DPLL_CMD_DEVICE_SET`` command by setting the ``enum dpll_feature_state`` +values for the attribute. +Once enabled the phase offset measurements for the input shall be returned +in the ``DPLL_A_PIN_PHASE_OFFSET`` attribute. + + =============================== ======================== + ``DPLL_A_PHASE_OFFSET_MONITOR`` attr state of a feature + =============================== ======================== + Embedded SYNC ============= diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 115d1a8f50bd..3bd6851c1d3c 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -240,6 +240,20 @@ definitions: integer part of a measured phase offset value. Value of (DPLL_A_PHASE_OFFSET % DPLL_PHASE_OFFSET_DIVIDER) is a fractional part of a measured phase offset value. + - + type: enum + name: feature-state + doc: | + Allow control (enable/disable) and status checking over features. + entries: + - + name: disable + doc: | + feature shall be disabled + - + name: enable + doc: | + feature shall be enabled attribute-sets: - @@ -293,6 +307,14 @@ attribute-sets: be put to message multiple times to indicate possible parallel quality levels (e.g. one specified by ITU option 1 and another one specified by option 2). + - + name: phase-offset-monitor + type: u32 + enum: feature-state + doc: Receive or request state of phase offset monitor feature. + If enabled, dpll device shall monitor and notify all currently + available inputs for changes of their phase offset against the + dpll device. - name: pin enum-name: dpll_a_pin @@ -483,6 +505,7 @@ operations: - temp - clock-id - type + - phase-offset-monitor dump: reply: *dev-attrs @@ -499,6 +522,7 @@ operations: request: attributes: - id + - phase-offset-monitor - name: device-create-ntf doc: Notification about device appearing diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index fe9b6893d261..8de90310c3be 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -37,8 +37,9 @@ static const struct nla_policy dpll_device_get_nl_policy[DPLL_A_ID + 1] = { }; /* DPLL_CMD_DEVICE_SET - do */ -static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_ID + 1] = { +static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_PHASE_OFFSET_MONITOR + 1] = { [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_PHASE_OFFSET_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1), }; /* DPLL_CMD_PIN_ID_GET - do */ @@ -105,7 +106,7 @@ static const struct genl_split_ops dpll_nl_ops[] = { .doit = dpll_nl_device_set_doit, .post_doit = dpll_post_doit, .policy = dpll_device_set_nl_policy, - .maxattr = DPLL_A_ID, + .maxattr = DPLL_A_PHASE_OFFSET_MONITOR, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index bf97d4b6d51f..349e1b3ca1ae 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -192,6 +192,17 @@ enum dpll_pin_capabilities { #define DPLL_PHASE_OFFSET_DIVIDER 1000 +/** + * enum dpll_feature_state - Allow control (enable/disable) and status checking + * over features. + * @DPLL_FEATURE_STATE_DISABLE: feature shall be disabled + * @DPLL_FEATURE_STATE_ENABLE: feature shall be enabled + */ +enum dpll_feature_state { + DPLL_FEATURE_STATE_DISABLE, + DPLL_FEATURE_STATE_ENABLE, +}; + enum dpll_a { DPLL_A_ID = 1, DPLL_A_MODULE_NAME, @@ -204,6 +215,7 @@ enum dpll_a { DPLL_A_TYPE, DPLL_A_LOCK_STATUS_ERROR, DPLL_A_CLOCK_QUALITY_LEVEL, + DPLL_A_PHASE_OFFSET_MONITOR, __DPLL_A_MAX, DPLL_A_MAX = (__DPLL_A_MAX - 1) -- cgit v1.2.3 From f8337efa4ff5a27e6c1d4e384166413eecd21a65 Mon Sep 17 00:00:00 2001 From: Petr Machata Date: Tue, 17 Jun 2025 00:44:19 +0200 Subject: vxlan: Support MC routing in the underlay Locally-generated MC packets have so far not been subject to MC routing. Instead an MC-enabled installation would maintain the MC routing tables, and separately from that the list of interfaces to send packets to as part of the VXLAN FDB and MDB. In a previous patch, a ip_mr_output() and ip6_mr_output() routines were added for IPv4 and IPv6. All locally generated MC traffic is now passed through these functions. For reasons of backward compatibility, an SKB (IPCB / IP6CB) flag guards the actual MC routing. This patch adds logic to set the flag, and the UAPI to enable the behavior. Signed-off-by: Petr Machata Reviewed-by: Ido Schimmel Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/d899655bb7e9b2521ee8c793e67056b9fd02ba12.1750113335.git.petrm@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 22 ++++++++++++++++++++-- include/net/vxlan.h | 5 ++++- include/uapi/linux/if_link.h | 1 + 3 files changed, 25 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index b22f9866be8e..a6cc1de4d8b8 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2451,6 +2451,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, rcu_read_lock(); if (addr_family == AF_INET) { struct vxlan_sock *sock4 = rcu_dereference(vxlan->vn4_sock); + u16 ipcb_flags = 0; struct rtable *rt; __be16 df = 0; __be32 saddr; @@ -2467,6 +2468,9 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, goto tx_error; } + if (flags & VXLAN_F_MC_ROUTE) + ipcb_flags |= IPSKB_MCROUTE; + if (!info) { /* Bypass encapsulation if the destination is local */ err = encap_bypass_if_local(skb, dev, vxlan, AF_INET, @@ -2522,11 +2526,13 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, udp_tunnel_xmit_skb(rt, sock4->sock->sk, skb, saddr, pkey->u.ipv4.dst, tos, ttl, df, - src_port, dst_port, xnet, !udp_sum, 0); + src_port, dst_port, xnet, !udp_sum, + ipcb_flags); #if IS_ENABLED(CONFIG_IPV6) } else { struct vxlan_sock *sock6 = rcu_dereference(vxlan->vn6_sock); struct in6_addr saddr; + u16 ip6cb_flags = 0; if (!ifindex) ifindex = sock6->sock->sk->sk_bound_dev_if; @@ -2542,6 +2548,9 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, goto tx_error; } + if (flags & VXLAN_F_MC_ROUTE) + ip6cb_flags |= IP6SKB_MCROUTE; + if (!info) { u32 rt6i_flags = dst_rt6_info(ndst)->rt6i_flags; @@ -2587,7 +2596,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, udp_tunnel6_xmit_skb(ndst, sock6->sock->sk, skb, dev, &saddr, &pkey->u.ipv6.dst, tos, ttl, pkey->label, src_port, dst_port, !udp_sum, - 0); + ip6cb_flags); #endif } vxlan_vnifilter_count(vxlan, vni, NULL, VXLAN_VNI_STATS_TX, pkt_len); @@ -3402,6 +3411,7 @@ static const struct nla_policy vxlan_policy[IFLA_VXLAN_MAX + 1] = { [IFLA_VXLAN_LOCALBYPASS] = NLA_POLICY_MAX(NLA_U8, 1), [IFLA_VXLAN_LABEL_POLICY] = NLA_POLICY_MAX(NLA_U32, VXLAN_LABEL_MAX), [IFLA_VXLAN_RESERVED_BITS] = NLA_POLICY_EXACT_LEN(sizeof(struct vxlanhdr)), + [IFLA_VXLAN_MC_ROUTE] = NLA_POLICY_MAX(NLA_U8, 1), }; static int vxlan_validate(struct nlattr *tb[], struct nlattr *data[], @@ -4315,6 +4325,14 @@ static int vxlan_nl2conf(struct nlattr *tb[], struct nlattr *data[], return err; } + if (data[IFLA_VXLAN_MC_ROUTE]) { + err = vxlan_nl2flag(conf, data, IFLA_VXLAN_MC_ROUTE, + VXLAN_F_MC_ROUTE, changelink, + true, extack); + if (err) + return err; + } + if (tb[IFLA_MTU]) { if (changelink) { NL_SET_ERR_MSG_ATTR(extack, tb[IFLA_MTU], diff --git a/include/net/vxlan.h b/include/net/vxlan.h index e2f7ca045d3e..0ee50785f4f1 100644 --- a/include/net/vxlan.h +++ b/include/net/vxlan.h @@ -332,6 +332,7 @@ struct vxlan_dev { #define VXLAN_F_VNIFILTER 0x20000 #define VXLAN_F_MDB 0x40000 #define VXLAN_F_LOCALBYPASS 0x80000 +#define VXLAN_F_MC_ROUTE 0x100000 /* Flags that are used in the receive path. These flags must match in * order for a socket to be shareable @@ -353,7 +354,9 @@ struct vxlan_dev { VXLAN_F_UDP_ZERO_CSUM6_RX | \ VXLAN_F_COLLECT_METADATA | \ VXLAN_F_VNIFILTER | \ - VXLAN_F_LOCALBYPASS) + VXLAN_F_LOCALBYPASS | \ + VXLAN_F_MC_ROUTE | \ + 0) struct net_device *vxlan_dev_create(struct net *net, const char *name, u8 name_assign_type, struct vxlan_config *conf); diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index 3ad2d5d98034..873c285996fe 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h @@ -1398,6 +1398,7 @@ enum { IFLA_VXLAN_LOCALBYPASS, IFLA_VXLAN_LABEL_POLICY, /* IPv6 flow label policy; ifla_vxlan_label_policy */ IFLA_VXLAN_RESERVED_BITS, + IFLA_VXLAN_MC_ROUTE, __IFLA_VXLAN_MAX }; #define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) -- cgit v1.2.3 From fc0e6db30941a66e284b8516b82356f97f31061d Mon Sep 17 00:00:00 2001 From: "Kory Maincent (Dent Project)" Date: Tue, 17 Jun 2025 14:12:01 +0200 Subject: net: pse-pd: Add support for reporting events Add support for devm_pse_irq_helper() to register PSE interrupts and report events such as over-current or over-temperature conditions. This follows a similar approach to the regulator API but also sends notifications using a dedicated PSE ethtool netlink socket. Signed-off-by: Kory Maincent (Dent Project) Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-2-78a1a645e2ee@bootlin.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/ethtool.yaml | 34 +++++ Documentation/networking/ethtool-netlink.rst | 19 +++ drivers/net/pse-pd/pse_core.c | 179 +++++++++++++++++++++++++ include/linux/ethtool_netlink.h | 7 + include/linux/pse-pd/pse.h | 20 +++ include/uapi/linux/ethtool_netlink_generated.h | 19 +++ net/ethtool/pse-pd.c | 39 ++++++ 7 files changed, 317 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index ed9bcdec01cc..92b34a19f308 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -118,6 +118,17 @@ definitions: doc: | Hardware timestamp comes from one PHY device of the network topology + - + name: pse-event + doc: PSE event list for the PSE controller + type: flags + entries: + - + name: over-current + doc: PSE output current is too high + - + name: over-temp + doc: PSE in over temperature state attribute-sets: - @@ -1555,6 +1566,19 @@ attribute-sets: name: hwtstamp-flags type: nest nested-attributes: bitset + - + name: pse-ntf + attr-cnt-name: --ethtool-a-pse-ntf-cnt + attributes: + - + name: header + type: nest + nested-attributes: header + - + name: events + type: uint + enum: pse-event + doc: List of events reported by the PSE controller operations: enum-model: directional @@ -2413,3 +2437,13 @@ operations: attributes: *tsconfig reply: attributes: *tsconfig + - + name: pse-ntf + doc: Notification for PSE events. + + attribute-set: pse-ntf + + event: + attributes: + - header + - events diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index b6e9af4d0f1b..433737865bc2 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -290,6 +290,7 @@ Kernel to userspace: ``ETHTOOL_MSG_PHY_NTF`` Ethernet PHY information change ``ETHTOOL_MSG_TSCONFIG_GET_REPLY`` hw timestamping configuration ``ETHTOOL_MSG_TSCONFIG_SET_REPLY`` new hw timestamping configuration + ``ETHTOOL_MSG_PSE_NTF`` PSE events notification ======================================== ================================= ``GET`` requests are sent by userspace applications to retrieve device @@ -1896,6 +1897,24 @@ various existing products that document power consumption in watts rather than classes. If power limit configuration based on classes is needed, the conversion can be done in user space, for example by ethtool. +PSE_NTF +======= + +Notify PSE events. + +Notification contents: + + =============================== ====== ======================== + ``ETHTOOL_A_PSE_HEADER`` nested request header + ``ETHTOOL_A_PSE_EVENTS`` bitset PSE events + =============================== ====== ======================== + +When set, the optional ``ETHTOOL_A_PSE_EVENTS`` attribute identifies the +PSE events. + +.. kernel-doc:: include/uapi/linux/ethtool_netlink_generated.h + :identifiers: ethtool_pse_event + RSS_GET ======= diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 4610c1f0ddd6..16cc1dc07246 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -7,10 +7,14 @@ #include #include +#include #include +#include #include #include #include +#include +#include static DEFINE_MUTEX(pse_list_mutex); static LIST_HEAD(pse_controller_list); @@ -210,6 +214,48 @@ out: return ret; } +/** + * pse_control_find_net_by_id - Find net attached to the pse control id + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * + * Return: pse_control pointer or NULL. The device returned has had a + * reference added and the pointer is safe until the user calls + * pse_control_put() to indicate they have finished with it. + */ +static struct pse_control * +pse_control_find_by_id(struct pse_controller_dev *pcdev, int id) +{ + struct pse_control *psec; + + mutex_lock(&pse_list_mutex); + list_for_each_entry(psec, &pcdev->pse_control_head, list) { + if (psec->id == id) { + kref_get(&psec->refcnt); + mutex_unlock(&pse_list_mutex); + return psec; + } + } + mutex_unlock(&pse_list_mutex); + return NULL; +} + +/** + * pse_control_get_netdev - Return netdev associated to a PSE control + * @psec: PSE control pointer + * + * Return: netdev pointer or NULL + */ +static struct net_device *pse_control_get_netdev(struct pse_control *psec) +{ + ASSERT_RTNL(); + + if (!psec || !psec->attached_phydev) + return NULL; + + return psec->attached_phydev->attached_dev; +} + static int pse_pi_is_enabled(struct regulator_dev *rdev) { struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); @@ -559,6 +605,139 @@ int devm_pse_controller_register(struct device *dev, } EXPORT_SYMBOL_GPL(devm_pse_controller_register); +struct pse_irq { + struct pse_controller_dev *pcdev; + struct pse_irq_desc desc; + unsigned long *notifs; +}; + +/** + * pse_to_regulator_notifs - Convert PSE notifications to Regulator + * notifications + * @notifs: PSE notifications + * + * Return: Regulator notifications + */ +static unsigned long pse_to_regulator_notifs(unsigned long notifs) +{ + unsigned long rnotifs = 0; + + if (notifs & ETHTOOL_PSE_EVENT_OVER_CURRENT) + rnotifs |= REGULATOR_EVENT_OVER_CURRENT; + if (notifs & ETHTOOL_PSE_EVENT_OVER_TEMP) + rnotifs |= REGULATOR_EVENT_OVER_TEMP; + + return rnotifs; +} + +/** + * pse_isr - IRQ handler for PSE + * @irq: irq number + * @data: pointer to user interrupt structure + * + * Return: irqreturn_t - status of IRQ + */ +static irqreturn_t pse_isr(int irq, void *data) +{ + struct pse_controller_dev *pcdev; + unsigned long notifs_mask = 0; + struct pse_irq_desc *desc; + struct pse_irq *h = data; + int ret, i; + + desc = &h->desc; + pcdev = h->pcdev; + + /* Clear notifs mask */ + memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs)); + mutex_lock(&pcdev->lock); + ret = desc->map_event(irq, pcdev, h->notifs, ¬ifs_mask); + mutex_unlock(&pcdev->lock); + if (ret || !notifs_mask) + return IRQ_NONE; + + for_each_set_bit(i, ¬ifs_mask, pcdev->nr_lines) { + unsigned long notifs, rnotifs; + struct net_device *netdev; + struct pse_control *psec; + + /* Do nothing PI not described */ + if (!pcdev->pi[i].rdev) + continue; + + notifs = h->notifs[i]; + dev_dbg(h->pcdev->dev, + "Sending PSE notification EVT 0x%lx\n", notifs); + + psec = pse_control_find_by_id(pcdev, i); + rtnl_lock(); + netdev = pse_control_get_netdev(psec); + if (netdev) + ethnl_pse_send_ntf(netdev, notifs); + rtnl_unlock(); + pse_control_put(psec); + + rnotifs = pse_to_regulator_notifs(notifs); + regulator_notifier_call_chain(pcdev->pi[i].rdev, rnotifs, + NULL); + } + + return IRQ_HANDLED; +} + +/** + * devm_pse_irq_helper - Register IRQ based PSE event notifier + * @pcdev: a pointer to the PSE + * @irq: the irq value to be passed to request_irq + * @irq_flags: the flags to be passed to request_irq + * @d: PSE interrupt description + * + * Return: 0 on success and errno on failure + */ +int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq, + int irq_flags, const struct pse_irq_desc *d) +{ + struct device *dev = pcdev->dev; + size_t irq_name_len; + struct pse_irq *h; + char *irq_name; + int ret; + + if (!d || !d->map_event || !d->name) + return -EINVAL; + + h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL); + if (!h) + return -ENOMEM; + + h->pcdev = pcdev; + h->desc = *d; + + /* IRQ name len is pcdev dev name + 5 char + irq desc name + 1 */ + irq_name_len = strlen(dev_name(pcdev->dev)) + 5 + strlen(d->name) + 1; + irq_name = devm_kzalloc(dev, irq_name_len, GFP_KERNEL); + if (!irq_name) + return -ENOMEM; + + snprintf(irq_name, irq_name_len, "pse-%s:%s", dev_name(pcdev->dev), + d->name); + + h->notifs = devm_kcalloc(dev, pcdev->nr_lines, + sizeof(*h->notifs), GFP_KERNEL); + if (!h->notifs) + return -ENOMEM; + + ret = devm_request_threaded_irq(dev, irq, NULL, pse_isr, + IRQF_ONESHOT | irq_flags, + irq_name, h); + if (ret) + dev_err(pcdev->dev, "Failed to request IRQ %d\n", irq); + + pcdev->irq = irq; + return ret; +} +EXPORT_SYMBOL_GPL(devm_pse_irq_helper); + /* PSE control section */ static void __pse_control_release(struct kref *kref) diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h index aba91335273a..1dcc4059b5ab 100644 --- a/include/linux/ethtool_netlink.h +++ b/include/linux/ethtool_netlink.h @@ -43,6 +43,8 @@ void ethtool_aggregate_rmon_stats(struct net_device *dev, struct ethtool_rmon_stats *rmon_stats); bool ethtool_dev_mm_supported(struct net_device *dev); +void ethnl_pse_send_ntf(struct net_device *netdev, unsigned long notif); + #else static inline int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd) { @@ -120,6 +122,11 @@ static inline bool ethtool_dev_mm_supported(struct net_device *dev) return false; } +static inline void ethnl_pse_send_ntf(struct phy_device *phydev, + unsigned long notif) +{ +} + #endif /* IS_ENABLED(CONFIG_ETHTOOL_NETLINK) */ static inline int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index 8b0866fad2ad..6eb064722aa8 100644 --- a/include/linux/pse-pd/pse.h +++ b/include/linux/pse-pd/pse.h @@ -7,12 +7,15 @@ #include #include +#include +#include /* Maximum current in uA according to IEEE 802.3-2022 Table 145-1 */ #define MAX_PI_CURRENT 1920000 /* Maximum power in mW according to IEEE 802.3-2022 Table 145-16 */ #define MAX_PI_PW 99900 +struct net_device; struct phy_device; struct pse_controller_dev; struct netlink_ext_ack; @@ -37,6 +40,19 @@ struct ethtool_c33_pse_pw_limit_range { u32 max; }; +/** + * struct pse_irq_desc - notification sender description for IRQ based events. + * + * @name: the visible name for the IRQ + * @map_event: driver callback to map IRQ status into PSE devices with events. + */ +struct pse_irq_desc { + const char *name; + int (*map_event)(int irq, struct pse_controller_dev *pcdev, + unsigned long *notifs, + unsigned long *notifs_mask); +}; + /** * struct pse_control_config - PSE control/channel configuration. * @@ -228,6 +244,7 @@ struct pse_pi { * @types: types of the PSE controller * @pi: table of PSE PIs described in this controller device * @no_of_pse_pi: flag set if the pse_pis devicetree node is not used + * @irq: PSE interrupt */ struct pse_controller_dev { const struct pse_controller_ops *ops; @@ -241,6 +258,7 @@ struct pse_controller_dev { enum ethtool_pse_types types; struct pse_pi *pi; bool no_of_pse_pi; + int irq; }; #if IS_ENABLED(CONFIG_PSE_CONTROLLER) @@ -249,6 +267,8 @@ void pse_controller_unregister(struct pse_controller_dev *pcdev); struct device; int devm_pse_controller_register(struct device *dev, struct pse_controller_dev *pcdev); +int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq, + int irq_flags, const struct pse_irq_desc *d); struct pse_control *of_pse_control_get(struct device_node *node, struct phy_device *phydev); diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 9a02f579de22..3864aa0de8c7 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -49,6 +49,16 @@ enum hwtstamp_source { HWTSTAMP_SOURCE_PHYLIB, }; +/** + * enum ethtool_pse_event - PSE event list for the PSE controller + * @ETHTOOL_PSE_EVENT_OVER_CURRENT: PSE output current is too high + * @ETHTOOL_PSE_EVENT_OVER_TEMP: PSE in over temperature state + */ +enum ethtool_pse_event { + ETHTOOL_PSE_EVENT_OVER_CURRENT = 1, + ETHTOOL_PSE_EVENT_OVER_TEMP = 2, +}; + enum { ETHTOOL_A_HEADER_UNSPEC, ETHTOOL_A_HEADER_DEV_INDEX, @@ -718,6 +728,14 @@ enum { ETHTOOL_A_TSCONFIG_MAX = (__ETHTOOL_A_TSCONFIG_CNT - 1) }; +enum { + ETHTOOL_A_PSE_NTF_HEADER = 1, + ETHTOOL_A_PSE_NTF_EVENTS, + + __ETHTOOL_A_PSE_NTF_CNT, + ETHTOOL_A_PSE_NTF_MAX = (__ETHTOOL_A_PSE_NTF_CNT - 1) +}; + enum { ETHTOOL_MSG_USER_NONE = 0, ETHTOOL_MSG_STRSET_GET = 1, @@ -822,6 +840,7 @@ enum { ETHTOOL_MSG_PHY_NTF, ETHTOOL_MSG_TSCONFIG_GET_REPLY, ETHTOOL_MSG_TSCONFIG_SET_REPLY, + ETHTOOL_MSG_PSE_NTF, __ETHTOOL_MSG_KERNEL_CNT, ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1) diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c index 4f6b99eab2a6..5443b4e0065a 100644 --- a/net/ethtool/pse-pd.c +++ b/net/ethtool/pse-pd.c @@ -315,3 +315,42 @@ const struct ethnl_request_ops ethnl_pse_request_ops = { .set = ethnl_set_pse, /* PSE has no notification */ }; + +void ethnl_pse_send_ntf(struct net_device *netdev, unsigned long notifs) +{ + void *reply_payload; + struct sk_buff *skb; + int reply_len; + int ret; + + ASSERT_RTNL(); + + if (!netdev || !notifs) + return; + + reply_len = ethnl_reply_header_size() + + nla_total_size(sizeof(u32)); /* _PSE_NTF_EVENTS */ + + skb = genlmsg_new(reply_len, GFP_KERNEL); + if (!skb) + return; + + reply_payload = ethnl_bcastmsg_put(skb, ETHTOOL_MSG_PSE_NTF); + if (!reply_payload) + goto err_skb; + + ret = ethnl_fill_reply_header(skb, netdev, ETHTOOL_A_PSE_NTF_HEADER); + if (ret < 0) + goto err_skb; + + if (nla_put_uint(skb, ETHTOOL_A_PSE_NTF_EVENTS, notifs)) + goto err_skb; + + genlmsg_end(skb, reply_payload); + ethnl_multicast(skb, netdev); + return; + +err_skb: + nlmsg_free(skb); +} +EXPORT_SYMBOL_GPL(ethnl_pse_send_ntf); -- cgit v1.2.3 From 1176978ed851952652ddea3685e2f71a0e5d61ff Mon Sep 17 00:00:00 2001 From: "Kory Maincent (Dent Project)" Date: Tue, 17 Jun 2025 14:12:04 +0200 Subject: net: ethtool: Add support for new power domains index description Report the index of the newly introduced PSE power domain to the user, enabling improved management of the power budget for PSE devices. Signed-off-by: Kory Maincent (Dent Project) Reviewed-by: Oleksij Rempel Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-5-78a1a645e2ee@bootlin.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/ethtool.yaml | 5 +++++ Documentation/networking/ethtool-netlink.rst | 4 ++++ drivers/net/pse-pd/pse_core.c | 3 +++ include/linux/pse-pd/pse.h | 2 ++ include/uapi/linux/ethtool_netlink_generated.h | 1 + net/ethtool/pse-pd.c | 7 +++++++ 6 files changed, 22 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index 92b34a19f308..dfd9b842a4e7 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -1406,6 +1406,10 @@ attribute-sets: type: nest multi-attr: true nested-attributes: c33-pse-pw-limit + - + name: pse-pw-d-id + type: u32 + name-prefix: ethtool-a- - name: rss attr-cnt-name: __ethtool-a-rss-cnt @@ -2229,6 +2233,7 @@ operations: - c33-pse-ext-substate - c33-pse-avail-pw-limit - c33-pse-pw-limit-ranges + - pse-pw-d-id dump: *pse-get-op - name: pse-set diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index 433737865bc2..e9af8e58564c 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -1789,6 +1789,7 @@ Kernel response contents: limit of the PoE PSE. ``ETHTOOL_A_C33_PSE_PW_LIMIT_RANGES`` nested Supported power limit configuration ranges. + ``ETHTOOL_A_PSE_PW_D_ID`` u32 Index of the PSE power domain ========================================== ====== ============================= When set, the optional ``ETHTOOL_A_PODL_PSE_ADMIN_STATE`` attribute identifies @@ -1862,6 +1863,9 @@ identifies the C33 PSE power limit ranges through If the controller works with fixed classes, the min and max values will be equal. +The ``ETHTOOL_A_PSE_PW_D_ID`` attribute identifies the index of PSE power +domain. + PSE_SET ======= diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index f2fb7ccbc4c2..7d424c22225e 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -1098,6 +1098,9 @@ int pse_ethtool_get_status(struct pse_control *psec, pcdev = psec->pcdev; ops = pcdev->ops; mutex_lock(&pcdev->lock); + if (pcdev->pi[psec->id].pw_d) + status->pw_d_id = pcdev->pi[psec->id].pw_d->id; + ret = ops->pi_get_admin_state(pcdev, psec->id, &admin_state); if (ret) goto out; diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index f736b1677ea5..2f8ecfd87d43 100644 --- a/include/linux/pse-pd/pse.h +++ b/include/linux/pse-pd/pse.h @@ -114,6 +114,7 @@ struct pse_pw_limit_ranges { /** * struct ethtool_pse_control_status - PSE control/channel status. * + * @pw_d_id: PSE power domain index. * @podl_admin_state: operational state of the PoDL PSE * functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState * @podl_pw_status: power detection status of the PoDL PSE. @@ -135,6 +136,7 @@ struct pse_pw_limit_ranges { * ranges */ struct ethtool_pse_control_status { + u32 pw_d_id; enum ethtool_podl_pse_admin_state podl_admin_state; enum ethtool_podl_pse_pw_d_status podl_pw_status; enum ethtool_c33_pse_admin_state c33_admin_state; diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 3864aa0de8c7..ed344c8533eb 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -652,6 +652,7 @@ enum { ETHTOOL_A_C33_PSE_EXT_SUBSTATE, ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT, ETHTOOL_A_C33_PSE_PW_LIMIT_RANGES, + ETHTOOL_A_PSE_PW_D_ID, __ETHTOOL_A_PSE_CNT, ETHTOOL_A_PSE_MAX = (__ETHTOOL_A_PSE_CNT - 1) diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c index 5443b4e0065a..6a978a55959e 100644 --- a/net/ethtool/pse-pd.c +++ b/net/ethtool/pse-pd.c @@ -83,6 +83,8 @@ static int pse_reply_size(const struct ethnl_req_info *req_base, const struct ethtool_pse_control_status *st = &data->status; int len = 0; + if (st->pw_d_id) + len += nla_total_size(sizeof(u32)); /* _PSE_PW_D_ID */ if (st->podl_admin_state > 0) len += nla_total_size(sizeof(u32)); /* _PODL_PSE_ADMIN_STATE */ if (st->podl_pw_status > 0) @@ -148,6 +150,11 @@ static int pse_fill_reply(struct sk_buff *skb, const struct pse_reply_data *data = PSE_REPDATA(reply_base); const struct ethtool_pse_control_status *st = &data->status; + if (st->pw_d_id && + nla_put_u32(skb, ETHTOOL_A_PSE_PW_D_ID, + st->pw_d_id)) + return -EMSGSIZE; + if (st->podl_admin_state > 0 && nla_put_u32(skb, ETHTOOL_A_PODL_PSE_ADMIN_STATE, st->podl_admin_state)) -- cgit v1.2.3 From ffef61d6d27374542f1bce4452200d9bdd2e1edd Mon Sep 17 00:00:00 2001 From: "Kory Maincent (Dent Project)" Date: Tue, 17 Jun 2025 14:12:06 +0200 Subject: net: pse-pd: Add support for budget evaluation strategies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch introduces the ability to configure the PSE PI budget evaluation strategies. Budget evaluation strategies is utilized by PSE controllers to determine which ports to turn off first in scenarios such as power budget exceedance. The pis_prio_max value is used to define the maximum priority level supported by the controller. Both the current priority and the maximum priority are exposed to the user through the pse_ethtool_get_status call. This patch add support for two mode of budget evaluation strategies. 1. Static Method: This method involves distributing power based on PD classification. It’s straightforward and stable, the PSE core keeping track of the budget and subtracting the power requested by each PD’s class. Advantages: Every PD gets its promised power at any time, which guarantees reliability. Disadvantages: PD classification steps are large, meaning devices request much more power than they actually need. As a result, the power supply may only operate at, say, 50% capacity, which is inefficient and wastes money. Priority max value is matching the number of PSE PIs within the PSE. 2. Dynamic Method: To address the inefficiencies of the static method, vendors like Microchip have introduced dynamic power budgeting, as seen in the PD692x0 firmware. This method monitors the current consumption per port and subtracts it from the available power budget. When the budget is exceeded, lower-priority ports are shut down. Advantages: This method optimizes resource utilization, saving costs. Disadvantages: Low-priority devices may experience instability. Priority max value is set by the PSE controller driver. For now, budget evaluation methods are not configurable and cannot be mixed. They are hardcoded in the PSE driver itself, as no current PSE controller supports both methods. Signed-off-by: Kory Maincent (Dent Project) Acked-by: Oleksij Rempel Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-7-78a1a645e2ee@bootlin.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/ethtool.yaml | 30 +- drivers/net/pse-pd/pse_core.c | 731 +++++++++++++++++++++++-- include/linux/pse-pd/pse.h | 76 +++ include/uapi/linux/ethtool_netlink_generated.h | 18 + 4 files changed, 815 insertions(+), 40 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index dfd9b842a4e7..7a9a857370e2 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -122,13 +122,39 @@ definitions: name: pse-event doc: PSE event list for the PSE controller type: flags + name-prefix: ethtool- entries: - - name: over-current + name: pse-event-over-current doc: PSE output current is too high - - name: over-temp + name: pse-event-over-temp doc: PSE in over temperature state + - + name: c33-pse-event-detection + doc: | + detection process occur on the PSE. IEEE 802.3-2022 33.2.5 and + 145.2.6 PSE detection of PDs. IEEE 802.3-202 30.9.1.1.5 + aPSEPowerDetectionStatus + - + name: c33-pse-event-classification + doc: | + classification process occur on the PSE. IEEE 802.3-2022 33.2.6 + and 145.2.8 classification of PDs mutual identification. + IEEE 802.3-2022 30.9.1.1.8 aPSEPowerClassification. + - + name: c33-pse-event-disconnection + doc: | + PD has been disconnected on the PSE. IEEE 802.3-2022 33.3.8 + and 145.3.9 PD Maintain Power Signature. IEEE 802.3-2022 + 33.5.1.2.9 MPS Absent. IEEE 802.3-2022 30.9.1.1.20 + aPSEMPSAbsentCounter. + - + name: pse-event-over-budget + doc: PSE turned off due to over budget situation + - + name: pse-event-sw-pw-control-error + doc: PSE faced an error managing the power control from software attribute-sets: - diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 495d72f98029..23eb3c9d0bcd 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -47,11 +47,14 @@ struct pse_control { * @id: ID of the power domain * @supply: Power supply the Power Domain * @refcnt: Number of gets of this pse_power_domain + * @budget_eval_strategy: Current power budget evaluation strategy of the + * power domain */ struct pse_power_domain { int id; struct regulator *supply; struct kref refcnt; + u32 budget_eval_strategy; }; static int of_load_single_pse_pi_pairset(struct device_node *node, @@ -297,6 +300,115 @@ static int pse_pi_is_hw_enabled(struct pse_controller_dev *pcdev, int id) return 0; } +/** + * pse_pi_is_admin_enable_pending - Check if PI is in admin enable pending state + * which mean the power is not yet being + * delivered + * @pcdev: a pointer to the PSE controller device + * @id: Index of the PI + * + * Detects if a PI is enabled in software with a PD detected, but the hardware + * admin state hasn't been applied yet. + * + * This function is used in the power delivery and retry mechanisms to determine + * which PIs need to have power delivery attempted again. + * + * Return: true if the PI has admin enable flag set in software but not yet + * reflected in the hardware admin state, false otherwise. + */ +static bool +pse_pi_is_admin_enable_pending(struct pse_controller_dev *pcdev, int id) +{ + int ret; + + /* PI not enabled or nothing is plugged */ + if (!pcdev->pi[id].admin_state_enabled || + !pcdev->pi[id].isr_pd_detected) + return false; + + ret = pse_pi_is_hw_enabled(pcdev, id); + /* PSE PI is already enabled at hardware level */ + if (ret == 1) + return false; + + return true; +} + +static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev, + int id, + struct netlink_ext_ack *extack); + +/** + * pse_pw_d_retry_power_delivery - Retry power delivery for pending ports in a + * PSE power domain + * @pcdev: a pointer to the PSE controller device + * @pw_d: a pointer to the PSE power domain + * + * Scans all ports in the specified power domain and attempts to enable power + * delivery to any ports that have admin enable state set but don't yet have + * hardware power enabled. Used when there are changes in connection status, + * admin state, or priority that might allow previously unpowered ports to + * receive power, especially in over-budget conditions. + */ +static void pse_pw_d_retry_power_delivery(struct pse_controller_dev *pcdev, + struct pse_power_domain *pw_d) +{ + int i, ret = 0; + + for (i = 0; i < pcdev->nr_lines; i++) { + int prio_max = pcdev->nr_lines; + struct netlink_ext_ack extack; + + if (pcdev->pi[i].pw_d != pw_d) + continue; + + if (!pse_pi_is_admin_enable_pending(pcdev, i)) + continue; + + /* Do not try to enable PI with a lower prio (higher value) + * than one which already can't be enabled. + */ + if (pcdev->pi[i].prio > prio_max) + continue; + + ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, i, &extack); + if (ret == -ERANGE) + prio_max = pcdev->pi[i].prio; + } +} + +/** + * pse_pw_d_is_sw_pw_control - Determine if power control is software managed + * @pcdev: a pointer to the PSE controller device + * @pw_d: a pointer to the PSE power domain + * + * This function determines whether the power control for a specific power + * domain is managed by software in the interrupt handler rather than directly + * by hardware. + * + * Software power control is active in the following cases: + * - When the budget evaluation strategy is set to static + * - When the budget evaluation strategy is disabled but the PSE controller + * has an interrupt handler that can report if a Powered Device is connected + * + * Return: true if the power control of the power domain is managed by software, + * false otherwise + */ +static bool pse_pw_d_is_sw_pw_control(struct pse_controller_dev *pcdev, + struct pse_power_domain *pw_d) +{ + if (!pw_d) + return false; + + if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC) + return true; + if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_DISABLED && + pcdev->ops->pi_enable && pcdev->irq) + return true; + + return false; +} + static int pse_pi_is_enabled(struct regulator_dev *rdev) { struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); @@ -309,17 +421,252 @@ static int pse_pi_is_enabled(struct regulator_dev *rdev) id = rdev_get_id(rdev); mutex_lock(&pcdev->lock); + if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) { + ret = pcdev->pi[id].admin_state_enabled; + goto out; + } + ret = pse_pi_is_hw_enabled(pcdev, id); + +out: mutex_unlock(&pcdev->lock); return ret; } +/** + * pse_pi_deallocate_pw_budget - Deallocate power budget of the PI + * @pi: a pointer to the PSE PI + */ +static void pse_pi_deallocate_pw_budget(struct pse_pi *pi) +{ + if (!pi->pw_d || !pi->pw_allocated_mW) + return; + + regulator_free_power_budget(pi->pw_d->supply, pi->pw_allocated_mW); + pi->pw_allocated_mW = 0; +} + +/** + * _pse_pi_disable - Call disable operation. Assumes the PSE lock has been + * acquired. + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * + * Return: 0 on success and failure value on error + */ +static int _pse_pi_disable(struct pse_controller_dev *pcdev, int id) +{ + const struct pse_controller_ops *ops = pcdev->ops; + int ret; + + if (!ops->pi_disable) + return -EOPNOTSUPP; + + ret = ops->pi_disable(pcdev, id); + if (ret) + return ret; + + pse_pi_deallocate_pw_budget(&pcdev->pi[id]); + + if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) + pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[id].pw_d); + + return 0; +} + +/** + * pse_disable_pi_pol - Disable a PI on a power budget policy + * @pcdev: a pointer to the PSE + * @id: index of the PSE PI + * + * Return: 0 on success and failure value on error + */ +static int pse_disable_pi_pol(struct pse_controller_dev *pcdev, int id) +{ + unsigned long notifs = ETHTOOL_PSE_EVENT_OVER_BUDGET; + struct pse_ntf ntf = {}; + int ret; + + dev_dbg(pcdev->dev, "Disabling PI %d to free power budget\n", id); + + ret = _pse_pi_disable(pcdev, id); + if (ret) + notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR; + + ntf.notifs = notifs; + ntf.id = id; + kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1, &pcdev->ntf_fifo_lock); + schedule_work(&pcdev->ntf_work); + + return ret; +} + +/** + * pse_disable_pi_prio - Disable all PIs of a given priority inside a PSE + * power domain + * @pcdev: a pointer to the PSE + * @pw_d: a pointer to the PSE power domain + * @prio: priority + * + * Return: 0 on success and failure value on error + */ +static int pse_disable_pi_prio(struct pse_controller_dev *pcdev, + struct pse_power_domain *pw_d, + int prio) +{ + int i; + + for (i = 0; i < pcdev->nr_lines; i++) { + int ret; + + if (pcdev->pi[i].prio != prio || + pcdev->pi[i].pw_d != pw_d || + pse_pi_is_hw_enabled(pcdev, i) <= 0) + continue; + + ret = pse_disable_pi_pol(pcdev, i); + if (ret) + return ret; + } + + return 0; +} + +/** + * pse_pi_allocate_pw_budget_static_prio - Allocate power budget for the PI + * when the budget eval strategy is + * static + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * @pw_req: power requested in mW + * @extack: extack for error reporting + * + * Allocates power using static budget evaluation strategy, where allocation + * is based on PD classification. When insufficient budget is available, + * lower-priority ports (higher priority numbers) are turned off first. + * + * Return: 0 on success and failure value on error + */ +static int +pse_pi_allocate_pw_budget_static_prio(struct pse_controller_dev *pcdev, int id, + int pw_req, struct netlink_ext_ack *extack) +{ + struct pse_pi *pi = &pcdev->pi[id]; + int ret, _prio; + + _prio = pcdev->nr_lines; + while (regulator_request_power_budget(pi->pw_d->supply, pw_req) == -ERANGE) { + if (_prio <= pi->prio) { + NL_SET_ERR_MSG_FMT(extack, + "PI %d: not enough power budget available", + id); + return -ERANGE; + } + + ret = pse_disable_pi_prio(pcdev, pi->pw_d, _prio); + if (ret < 0) + return ret; + + _prio--; + } + + pi->pw_allocated_mW = pw_req; + return 0; +} + +/** + * pse_pi_allocate_pw_budget - Allocate power budget for the PI + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * @pw_req: power requested in mW + * @extack: extack for error reporting + * + * Return: 0 on success and failure value on error + */ +static int pse_pi_allocate_pw_budget(struct pse_controller_dev *pcdev, int id, + int pw_req, struct netlink_ext_ack *extack) +{ + struct pse_pi *pi = &pcdev->pi[id]; + + if (!pi->pw_d) + return 0; + + /* PSE_BUDGET_EVAL_STRAT_STATIC */ + if (pi->pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC) + return pse_pi_allocate_pw_budget_static_prio(pcdev, id, pw_req, + extack); + + return 0; +} + +/** + * _pse_pi_delivery_power_sw_pw_ctrl - Enable PSE PI in case of software power + * control. Assumes the PSE lock has been + * acquired. + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * @extack: extack for error reporting + * + * Return: 0 on success and failure value on error + */ +static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev, + int id, + struct netlink_ext_ack *extack) +{ + const struct pse_controller_ops *ops = pcdev->ops; + struct pse_pi *pi = &pcdev->pi[id]; + int ret, pw_req; + + if (!ops->pi_get_pw_req) { + /* No power allocation management */ + ret = ops->pi_enable(pcdev, id); + if (ret) + NL_SET_ERR_MSG_FMT(extack, + "PI %d: enable error %d", + id, ret); + return ret; + } + + ret = ops->pi_get_pw_req(pcdev, id); + if (ret < 0) + return ret; + + pw_req = ret; + + /* Compare requested power with port power limit and use the lowest + * one. + */ + if (ops->pi_get_pw_limit) { + ret = ops->pi_get_pw_limit(pcdev, id); + if (ret < 0) + return ret; + + if (ret < pw_req) + pw_req = ret; + } + + ret = pse_pi_allocate_pw_budget(pcdev, id, pw_req, extack); + if (ret) + return ret; + + ret = ops->pi_enable(pcdev, id); + if (ret) { + pse_pi_deallocate_pw_budget(pi); + NL_SET_ERR_MSG_FMT(extack, + "PI %d: enable error %d", + id, ret); + return ret; + } + + return 0; +} + static int pse_pi_enable(struct regulator_dev *rdev) { struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); const struct pse_controller_ops *ops; - int id, ret; + int id, ret = 0; ops = pcdev->ops; if (!ops->pi_enable) @@ -327,6 +674,23 @@ static int pse_pi_enable(struct regulator_dev *rdev) id = rdev_get_id(rdev); mutex_lock(&pcdev->lock); + if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) { + /* Manage enabled status by software. + * Real enable process will happen if a port is connected. + */ + if (pcdev->pi[id].isr_pd_detected) { + struct netlink_ext_ack extack; + + ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id, &extack); + } + if (!ret || ret == -ERANGE) { + pcdev->pi[id].admin_state_enabled = 1; + ret = 0; + } + mutex_unlock(&pcdev->lock); + return ret; + } + ret = ops->pi_enable(pcdev, id); if (!ret) pcdev->pi[id].admin_state_enabled = 1; @@ -338,21 +702,18 @@ static int pse_pi_enable(struct regulator_dev *rdev) static int pse_pi_disable(struct regulator_dev *rdev) { struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); - const struct pse_controller_ops *ops; + struct pse_pi *pi; int id, ret; - ops = pcdev->ops; - if (!ops->pi_disable) - return -EOPNOTSUPP; - id = rdev_get_id(rdev); + pi = &pcdev->pi[id]; mutex_lock(&pcdev->lock); - ret = ops->pi_disable(pcdev, id); + ret = _pse_pi_disable(pcdev, id); if (!ret) - pcdev->pi[id].admin_state_enabled = 0; - mutex_unlock(&pcdev->lock); + pi->admin_state_enabled = 0; - return ret; + mutex_unlock(&pcdev->lock); + return 0; } static int _pse_pi_get_voltage(struct regulator_dev *rdev) @@ -628,6 +989,11 @@ static int pse_register_pw_ds(struct pse_controller_dev *pcdev) } pw_d->supply = supply; + if (pcdev->supp_budget_eval_strategies) + pw_d->budget_eval_strategy = pcdev->supp_budget_eval_strategies; + else + pw_d->budget_eval_strategy = PSE_BUDGET_EVAL_STRAT_DISABLED; + kref_init(&pw_d->refcnt); pcdev->pi[i].pw_d = pw_d; } @@ -636,6 +1002,34 @@ out: return ret; } +/** + * pse_send_ntf_worker - Worker to send PSE notifications + * @work: work object + * + * Manage and send PSE netlink notifications using a workqueue to avoid + * deadlock between pcdev_lock and pse_list_mutex. + */ +static void pse_send_ntf_worker(struct work_struct *work) +{ + struct pse_controller_dev *pcdev; + struct pse_ntf ntf; + + pcdev = container_of(work, struct pse_controller_dev, ntf_work); + + while (kfifo_out(&pcdev->ntf_fifo, &ntf, 1)) { + struct net_device *netdev; + struct pse_control *psec; + + psec = pse_control_find_by_id(pcdev, ntf.id); + rtnl_lock(); + netdev = pse_control_get_netdev(psec); + if (netdev) + ethnl_pse_send_ntf(netdev, ntf.notifs); + rtnl_unlock(); + pse_control_put(psec); + } +} + /** * pse_controller_register - register a PSE controller device * @pcdev: a pointer to the initialized PSE controller device @@ -649,6 +1043,13 @@ int pse_controller_register(struct pse_controller_dev *pcdev) mutex_init(&pcdev->lock); INIT_LIST_HEAD(&pcdev->pse_control_head); + spin_lock_init(&pcdev->ntf_fifo_lock); + ret = kfifo_alloc(&pcdev->ntf_fifo, pcdev->nr_lines, GFP_KERNEL); + if (ret) { + dev_err(pcdev->dev, "failed to allocate kfifo notifications\n"); + return ret; + } + INIT_WORK(&pcdev->ntf_work, pse_send_ntf_worker); if (!pcdev->nr_lines) pcdev->nr_lines = 1; @@ -715,6 +1116,10 @@ void pse_controller_unregister(struct pse_controller_dev *pcdev) { pse_flush_pw_ds(pcdev); pse_release_pis(pcdev); + if (pcdev->irq) + disable_irq(pcdev->irq); + cancel_work_sync(&pcdev->ntf_work); + kfifo_free(&pcdev->ntf_fifo); mutex_lock(&pse_list_mutex); list_del(&pcdev->list); mutex_unlock(&pse_list_mutex); @@ -786,6 +1191,52 @@ static unsigned long pse_to_regulator_notifs(unsigned long notifs) return rnotifs; } +/** + * pse_set_config_isr - Set PSE control config according to the PSE + * notifications + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * @notifs: PSE event notifications + * + * Return: 0 on success and failure value on error + */ +static int pse_set_config_isr(struct pse_controller_dev *pcdev, int id, + unsigned long notifs) +{ + int ret = 0; + + if (notifs & PSE_BUDGET_EVAL_STRAT_DYNAMIC) + return 0; + + if ((notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) && + ((notifs & ETHTOOL_C33_PSE_EVENT_DETECTION) || + (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION))) { + dev_dbg(pcdev->dev, + "PI %d: error, connection and disconnection reported simultaneously", + id); + return -EINVAL; + } + + if (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION) { + struct netlink_ext_ack extack; + + pcdev->pi[id].isr_pd_detected = true; + if (pcdev->pi[id].admin_state_enabled) { + ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id, + &extack); + if (ret == -ERANGE) + ret = 0; + } + } else if (notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) { + if (pcdev->pi[id].admin_state_enabled && + pcdev->pi[id].isr_pd_detected) + ret = _pse_pi_disable(pcdev, id); + pcdev->pi[id].isr_pd_detected = false; + } + + return ret; +} + /** * pse_isr - IRQ handler for PSE * @irq: irq number @@ -808,36 +1259,42 @@ static irqreturn_t pse_isr(int irq, void *data) memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs)); mutex_lock(&pcdev->lock); ret = desc->map_event(irq, pcdev, h->notifs, ¬ifs_mask); - mutex_unlock(&pcdev->lock); - if (ret || !notifs_mask) + if (ret || !notifs_mask) { + mutex_unlock(&pcdev->lock); return IRQ_NONE; + } for_each_set_bit(i, ¬ifs_mask, pcdev->nr_lines) { unsigned long notifs, rnotifs; - struct net_device *netdev; - struct pse_control *psec; + struct pse_ntf ntf = {}; /* Do nothing PI not described */ if (!pcdev->pi[i].rdev) continue; notifs = h->notifs[i]; + if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[i].pw_d)) { + ret = pse_set_config_isr(pcdev, i, notifs); + if (ret) + notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR; + } + dev_dbg(h->pcdev->dev, "Sending PSE notification EVT 0x%lx\n", notifs); - psec = pse_control_find_by_id(pcdev, i); - rtnl_lock(); - netdev = pse_control_get_netdev(psec); - if (netdev) - ethnl_pse_send_ntf(netdev, notifs); - rtnl_unlock(); - pse_control_put(psec); + ntf.notifs = notifs; + ntf.id = i; + kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1, + &pcdev->ntf_fifo_lock); + schedule_work(&pcdev->ntf_work); rnotifs = pse_to_regulator_notifs(notifs); regulator_notifier_call_chain(pcdev->pi[i].rdev, rnotifs, NULL); } + mutex_unlock(&pcdev->lock); + return IRQ_HANDLED; } @@ -960,6 +1417,20 @@ pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index, goto free_psec; } + if (!pcdev->ops->pi_get_admin_state) { + ret = -EOPNOTSUPP; + goto free_psec; + } + + /* Initialize admin_state_enabled before the regulator_get. This + * aims to have the right value reported in the first is_enabled + * call in case of control managed by software. + */ + ret = pse_pi_is_hw_enabled(pcdev, index); + if (ret < 0) + goto free_psec; + + pcdev->pi[index].admin_state_enabled = ret; psec->ps = devm_regulator_get_exclusive(pcdev->dev, rdev_get_name(pcdev->pi[index].rdev)); if (IS_ERR(psec->ps)) { @@ -967,12 +1438,6 @@ pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index, goto put_module; } - ret = regulator_is_enabled(psec->ps); - if (ret < 0) - goto regulator_put; - - pcdev->pi[index].admin_state_enabled = ret; - psec->pcdev = pcdev; list_add(&psec->list, &pcdev->pse_control_head); psec->id = index; @@ -981,8 +1446,6 @@ pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index, return psec; -regulator_put: - devm_regulator_put(psec->ps); put_module: module_put(pcdev->owner); free_psec: @@ -1093,6 +1556,35 @@ out: } EXPORT_SYMBOL_GPL(of_pse_control_get); +/** + * pse_get_sw_admin_state - Convert the software admin state to c33 or podl + * admin state value used in the standard + * @psec: PSE control pointer + * @admin_state: a pointer to the admin_state structure + */ +static void pse_get_sw_admin_state(struct pse_control *psec, + struct pse_admin_state *admin_state) +{ + struct pse_pi *pi = &psec->pcdev->pi[psec->id]; + + if (pse_has_podl(psec)) { + if (pi->admin_state_enabled) + admin_state->podl_admin_state = + ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED; + else + admin_state->podl_admin_state = + ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED; + } + if (pse_has_c33(psec)) { + if (pi->admin_state_enabled) + admin_state->c33_admin_state = + ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; + else + admin_state->c33_admin_state = + ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; + } +} + /** * pse_ethtool_get_status - get status of PSE control * @psec: PSE control pointer @@ -1109,19 +1601,46 @@ int pse_ethtool_get_status(struct pse_control *psec, struct pse_pw_status pw_status = {0}; const struct pse_controller_ops *ops; struct pse_controller_dev *pcdev; + struct pse_pi *pi; int ret; pcdev = psec->pcdev; ops = pcdev->ops; + + pi = &pcdev->pi[psec->id]; mutex_lock(&pcdev->lock); - if (pcdev->pi[psec->id].pw_d) - status->pw_d_id = pcdev->pi[psec->id].pw_d->id; + if (pi->pw_d) { + status->pw_d_id = pi->pw_d->id; + if (pse_pw_d_is_sw_pw_control(pcdev, pi->pw_d)) { + pse_get_sw_admin_state(psec, &admin_state); + } else { + ret = ops->pi_get_admin_state(pcdev, psec->id, + &admin_state); + if (ret) + goto out; + } + status->podl_admin_state = admin_state.podl_admin_state; + status->c33_admin_state = admin_state.c33_admin_state; - ret = ops->pi_get_admin_state(pcdev, psec->id, &admin_state); - if (ret) - goto out; - status->podl_admin_state = admin_state.podl_admin_state; - status->c33_admin_state = admin_state.c33_admin_state; + switch (pi->pw_d->budget_eval_strategy) { + case PSE_BUDGET_EVAL_STRAT_STATIC: + status->prio_max = pcdev->nr_lines - 1; + status->prio = pi->prio; + break; + case PSE_BUDGET_EVAL_STRAT_DYNAMIC: + status->prio_max = pcdev->pis_prio_max; + if (ops->pi_get_prio) { + ret = ops->pi_get_prio(pcdev, psec->id); + if (ret < 0) + goto out; + + status->prio = ret; + } + break; + default: + break; + } + } ret = ops->pi_get_pw_status(pcdev, psec->id, &pw_status); if (ret) @@ -1270,6 +1789,52 @@ int pse_ethtool_set_config(struct pse_control *psec, } EXPORT_SYMBOL_GPL(pse_ethtool_set_config); +/** + * pse_pi_update_pw_budget - Update PSE power budget allocated with new + * power in mW + * @pcdev: a pointer to the PSE controller device + * @id: index of the PSE PI + * @pw_req: power requested + * @extack: extack for reporting useful error messages + * + * Return: Previous power allocated on success and failure value on error + */ +static int pse_pi_update_pw_budget(struct pse_controller_dev *pcdev, int id, + const unsigned int pw_req, + struct netlink_ext_ack *extack) +{ + struct pse_pi *pi = &pcdev->pi[id]; + int previous_pw_allocated; + int pw_diff, ret = 0; + + /* We don't want pw_allocated_mW value change in the middle of an + * power budget update + */ + mutex_lock(&pcdev->lock); + previous_pw_allocated = pi->pw_allocated_mW; + pw_diff = pw_req - previous_pw_allocated; + if (!pw_diff) { + goto out; + } else if (pw_diff > 0) { + ret = regulator_request_power_budget(pi->pw_d->supply, pw_diff); + if (ret) { + NL_SET_ERR_MSG_FMT(extack, + "PI %d: not enough power budget available", + id); + goto out; + } + + } else { + regulator_free_power_budget(pi->pw_d->supply, -pw_diff); + } + pi->pw_allocated_mW = pw_req; + ret = previous_pw_allocated; + +out: + mutex_unlock(&pcdev->lock); + return ret; +} + /** * pse_ethtool_set_pw_limit - set PSE control power limit * @psec: PSE control pointer @@ -1282,7 +1847,7 @@ int pse_ethtool_set_pw_limit(struct pse_control *psec, struct netlink_ext_ack *extack, const unsigned int pw_limit) { - int uV, uA, ret; + int uV, uA, ret, previous_pw_allocated = 0; s64 tmp_64; if (pw_limit > MAX_PI_PW) @@ -1306,10 +1871,100 @@ int pse_ethtool_set_pw_limit(struct pse_control *psec, /* uA = mW * 1000000000 / uV */ uA = DIV_ROUND_CLOSEST_ULL(tmp_64, uV); - return regulator_set_current_limit(psec->ps, 0, uA); + /* Update power budget only in software power control case and + * if a Power Device is powered. + */ + if (pse_pw_d_is_sw_pw_control(psec->pcdev, + psec->pcdev->pi[psec->id].pw_d) && + psec->pcdev->pi[psec->id].admin_state_enabled && + psec->pcdev->pi[psec->id].isr_pd_detected) { + ret = pse_pi_update_pw_budget(psec->pcdev, psec->id, + pw_limit, extack); + if (ret < 0) + return ret; + previous_pw_allocated = ret; + } + + ret = regulator_set_current_limit(psec->ps, 0, uA); + if (ret < 0 && previous_pw_allocated) { + pse_pi_update_pw_budget(psec->pcdev, psec->id, + previous_pw_allocated, extack); + } + + return ret; } EXPORT_SYMBOL_GPL(pse_ethtool_set_pw_limit); +/** + * pse_ethtool_set_prio - Set PSE PI priority according to the budget + * evaluation strategy + * @psec: PSE control pointer + * @extack: extack for reporting useful error messages + * @prio: priovity value + * + * Return: 0 on success and failure value on error + */ +int pse_ethtool_set_prio(struct pse_control *psec, + struct netlink_ext_ack *extack, + unsigned int prio) +{ + struct pse_controller_dev *pcdev = psec->pcdev; + const struct pse_controller_ops *ops; + int ret = 0; + + if (!pcdev->pi[psec->id].pw_d) { + NL_SET_ERR_MSG(extack, "no power domain attached"); + return -EOPNOTSUPP; + } + + /* We don't want priority change in the middle of an + * enable/disable call or a priority mode change + */ + mutex_lock(&pcdev->lock); + switch (pcdev->pi[psec->id].pw_d->budget_eval_strategy) { + case PSE_BUDGET_EVAL_STRAT_STATIC: + if (prio >= pcdev->nr_lines) { + NL_SET_ERR_MSG_FMT(extack, + "priority %d exceed priority max %d", + prio, pcdev->nr_lines); + ret = -ERANGE; + goto out; + } + + pcdev->pi[psec->id].prio = prio; + pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[psec->id].pw_d); + break; + + case PSE_BUDGET_EVAL_STRAT_DYNAMIC: + ops = psec->pcdev->ops; + if (!ops->pi_set_prio) { + NL_SET_ERR_MSG(extack, + "pse driver does not support setting port priority"); + ret = -EOPNOTSUPP; + goto out; + } + + if (prio > pcdev->pis_prio_max) { + NL_SET_ERR_MSG_FMT(extack, + "priority %d exceed priority max %d", + prio, pcdev->pis_prio_max); + ret = -ERANGE; + goto out; + } + + ret = ops->pi_set_prio(pcdev, psec->id, prio); + break; + + default: + ret = -EOPNOTSUPP; + } + +out: + mutex_unlock(&pcdev->lock); + return ret; +} +EXPORT_SYMBOL_GPL(pse_ethtool_set_prio); + bool pse_has_podl(struct pse_control *psec) { return psec->pcdev->types & ETHTOOL_PSE_PODL; diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index 2f8ecfd87d43..e5f305cef82e 100644 --- a/include/linux/pse-pd/pse.h +++ b/include/linux/pse-pd/pse.h @@ -6,6 +6,8 @@ #define _LINUX_PSE_CONTROLLER_H #include +#include +#include #include #include #include @@ -134,6 +136,9 @@ struct pse_pw_limit_ranges { * is in charge of the memory allocation * @c33_pw_limit_nb_ranges: number of supported power limit configuration * ranges + * @prio_max: max priority allowed for the c33_prio variable value. + * @prio: priority of the PSE. Managed by PSE core in case of static budget + * evaluation strategy. */ struct ethtool_pse_control_status { u32 pw_d_id; @@ -147,6 +152,8 @@ struct ethtool_pse_control_status { u32 c33_avail_pw_limit; struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges; u32 c33_pw_limit_nb_ranges; + u32 prio_max; + u32 prio; }; /** @@ -170,6 +177,11 @@ struct ethtool_pse_control_status { * range. The driver is in charge of the memory * allocation and should return the number of * ranges. + * @pi_get_prio: Get the PSE PI priority. + * @pi_set_prio: Configure the PSE PI priority. + * @pi_get_pw_req: Get the power requested by a PD before enabling the PSE PI. + * This is only relevant when an interrupt is registered using + * devm_pse_irq_helper helper. */ struct pse_controller_ops { int (*setup_pi_matrix)(struct pse_controller_dev *pcdev); @@ -190,6 +202,10 @@ struct pse_controller_ops { int id, int max_mW); int (*pi_get_pw_limit_ranges)(struct pse_controller_dev *pcdev, int id, struct pse_pw_limit_ranges *pw_limit_ranges); + int (*pi_get_prio)(struct pse_controller_dev *pcdev, int id); + int (*pi_set_prio)(struct pse_controller_dev *pcdev, int id, + unsigned int prio); + int (*pi_get_pw_req)(struct pse_controller_dev *pcdev, int id); }; struct module; @@ -225,6 +241,13 @@ struct pse_pi_pairset { * @rdev: regulator represented by the PSE PI * @admin_state_enabled: PI enabled state * @pw_d: Power domain of the PSE PI + * @prio: Priority of the PSE PI. Used in static budget evaluation strategy + * @isr_pd_detected: PSE PI detection status managed by the interruption + * handler. This variable is relevant when the power enabled + * management is managed in software like the static + * budget evaluation strategy. + * @pw_allocated_mW: Power allocated to a PSE PI to manage power budget in + * static budget evaluation strategy. */ struct pse_pi { struct pse_pi_pairset pairset[2]; @@ -232,6 +255,20 @@ struct pse_pi { struct regulator_dev *rdev; bool admin_state_enabled; struct pse_power_domain *pw_d; + int prio; + bool isr_pd_detected; + int pw_allocated_mW; +}; + +/** + * struct pse_ntf - PSE notification element + * + * @id: ID of the PSE control + * @notifs: PSE notifications to be reported + */ +struct pse_ntf { + int id; + unsigned long notifs; }; /** @@ -249,6 +286,12 @@ struct pse_pi { * @pi: table of PSE PIs described in this controller device * @no_of_pse_pi: flag set if the pse_pis devicetree node is not used * @irq: PSE interrupt + * @pis_prio_max: Maximum value allowed for the PSE PIs priority + * @supp_budget_eval_strategies: budget evaluation strategies supported + * by the PSE + * @ntf_work: workqueue for PSE notification management + * @ntf_fifo: PSE notifications FIFO + * @ntf_fifo_lock: protect @ntf_fifo writer */ struct pse_controller_dev { const struct pse_controller_ops *ops; @@ -263,6 +306,29 @@ struct pse_controller_dev { struct pse_pi *pi; bool no_of_pse_pi; int irq; + unsigned int pis_prio_max; + u32 supp_budget_eval_strategies; + struct work_struct ntf_work; + DECLARE_KFIFO_PTR(ntf_fifo, struct pse_ntf); + spinlock_t ntf_fifo_lock; /* Protect @ntf_fifo writer */ +}; + +/** + * enum pse_budget_eval_strategies - PSE budget evaluation strategies. + * @PSE_BUDGET_EVAL_STRAT_DISABLED: Budget evaluation strategy disabled. + * @PSE_BUDGET_EVAL_STRAT_STATIC: PSE static budget evaluation strategy. + * Budget evaluation strategy based on the power requested during PD + * classification. This strategy is managed by the PSE core. + * @PSE_BUDGET_EVAL_STRAT_DYNAMIC: PSE dynamic budget evaluation + * strategy. Budget evaluation strategy based on the current consumption + * per ports compared to the total power budget. This mode is managed by + * the PSE controller. + */ + +enum pse_budget_eval_strategies { + PSE_BUDGET_EVAL_STRAT_DISABLED = 1 << 0, + PSE_BUDGET_EVAL_STRAT_STATIC = 1 << 1, + PSE_BUDGET_EVAL_STRAT_DYNAMIC = 1 << 2, }; #if IS_ENABLED(CONFIG_PSE_CONTROLLER) @@ -287,6 +353,9 @@ int pse_ethtool_set_config(struct pse_control *psec, int pse_ethtool_set_pw_limit(struct pse_control *psec, struct netlink_ext_ack *extack, const unsigned int pw_limit); +int pse_ethtool_set_prio(struct pse_control *psec, + struct netlink_ext_ack *extack, + unsigned int prio); bool pse_has_podl(struct pse_control *psec); bool pse_has_c33(struct pse_control *psec); @@ -324,6 +393,13 @@ static inline int pse_ethtool_set_pw_limit(struct pse_control *psec, return -EOPNOTSUPP; } +static inline int pse_ethtool_set_prio(struct pse_control *psec, + struct netlink_ext_ack *extack, + unsigned int prio) +{ + return -EOPNOTSUPP; +} + static inline bool pse_has_podl(struct pse_control *psec) { return false; diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index ed344c8533eb..c6a95224be25 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -53,10 +53,28 @@ enum hwtstamp_source { * enum ethtool_pse_event - PSE event list for the PSE controller * @ETHTOOL_PSE_EVENT_OVER_CURRENT: PSE output current is too high * @ETHTOOL_PSE_EVENT_OVER_TEMP: PSE in over temperature state + * @ETHTOOL_C33_PSE_EVENT_DETECTION: detection process occur on the PSE. IEEE + * 802.3-2022 33.2.5 and 145.2.6 PSE detection of PDs. IEEE 802.3-202 + * 30.9.1.1.5 aPSEPowerDetectionStatus + * @ETHTOOL_C33_PSE_EVENT_CLASSIFICATION: classification process occur on the + * PSE. IEEE 802.3-2022 33.2.6 and 145.2.8 classification of PDs mutual + * identification. IEEE 802.3-2022 30.9.1.1.8 aPSEPowerClassification. + * @ETHTOOL_C33_PSE_EVENT_DISCONNECTION: PD has been disconnected on the PSE. + * IEEE 802.3-2022 33.3.8 and 145.3.9 PD Maintain Power Signature. IEEE + * 802.3-2022 33.5.1.2.9 MPS Absent. IEEE 802.3-2022 30.9.1.1.20 + * aPSEMPSAbsentCounter. + * @ETHTOOL_PSE_EVENT_OVER_BUDGET: PSE turned off due to over budget situation + * @ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR: PSE faced an error managing the + * power control from software */ enum ethtool_pse_event { ETHTOOL_PSE_EVENT_OVER_CURRENT = 1, ETHTOOL_PSE_EVENT_OVER_TEMP = 2, + ETHTOOL_C33_PSE_EVENT_DETECTION = 4, + ETHTOOL_C33_PSE_EVENT_CLASSIFICATION = 8, + ETHTOOL_C33_PSE_EVENT_DISCONNECTION = 16, + ETHTOOL_PSE_EVENT_OVER_BUDGET = 32, + ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR = 64, }; enum { -- cgit v1.2.3 From eeb0c8f72f49a21984981188404cfd3700edbaff Mon Sep 17 00:00:00 2001 From: "Kory Maincent (Dent Project)" Date: Tue, 17 Jun 2025 14:12:07 +0200 Subject: net: ethtool: Add PSE port priority support feature This patch expands the status information provided by ethtool for PSE c33 with current port priority and max port priority. It also adds a call to pse_ethtool_set_prio() to configure the PSE port priority. Signed-off-by: Kory Maincent (Dent Project) Reviewed-by: Oleksij Rempel Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-8-78a1a645e2ee@bootlin.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/ethtool.yaml | 11 +++++++++++ Documentation/networking/ethtool-netlink.rst | 26 ++++++++++++++++++++++++++ include/uapi/linux/ethtool_netlink_generated.h | 2 ++ net/ethtool/pse-pd.c | 18 ++++++++++++++++++ 4 files changed, 57 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index 7a9a857370e2..e6a77e8053a0 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -1436,6 +1436,14 @@ attribute-sets: name: pse-pw-d-id type: u32 name-prefix: ethtool-a- + - + name: pse-prio-max + type: u32 + name-prefix: ethtool-a- + - + name: pse-prio + type: u32 + name-prefix: ethtool-a- - name: rss attr-cnt-name: __ethtool-a-rss-cnt @@ -2260,6 +2268,8 @@ operations: - c33-pse-avail-pw-limit - c33-pse-pw-limit-ranges - pse-pw-d-id + - pse-prio-max + - pse-prio dump: *pse-get-op - name: pse-set @@ -2274,6 +2284,7 @@ operations: - podl-pse-admin-control - c33-pse-admin-control - c33-pse-avail-pw-limit + - pse-prio - name: rss-get doc: Get RSS params. diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index e9af8e58564c..e45bb555e909 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -1790,6 +1790,10 @@ Kernel response contents: ``ETHTOOL_A_C33_PSE_PW_LIMIT_RANGES`` nested Supported power limit configuration ranges. ``ETHTOOL_A_PSE_PW_D_ID`` u32 Index of the PSE power domain + ``ETHTOOL_A_PSE_PRIO_MAX`` u32 Priority maximum configurable + on the PoE PSE + ``ETHTOOL_A_PSE_PRIO`` u32 Priority of the PoE PSE + currently configured ========================================== ====== ============================= When set, the optional ``ETHTOOL_A_PODL_PSE_ADMIN_STATE`` attribute identifies @@ -1866,6 +1870,12 @@ equal. The ``ETHTOOL_A_PSE_PW_D_ID`` attribute identifies the index of PSE power domain. +When set, the optional ``ETHTOOL_A_PSE_PRIO_MAX`` attribute identifies +the PSE maximum priority value. +When set, the optional ``ETHTOOL_A_PSE_PRIO`` attributes is used to +identifies the currently configured PSE priority. +For a description of PSE priority attributes, see ``PSE_SET``. + PSE_SET ======= @@ -1879,6 +1889,8 @@ Request contents: ``ETHTOOL_A_C33_PSE_ADMIN_CONTROL`` u32 Control PSE Admin state ``ETHTOOL_A_C33_PSE_AVAIL_PWR_LIMIT`` u32 Control PoE PSE available power limit + ``ETHTOOL_A_PSE_PRIO`` u32 Control priority of the + PoE PSE ====================================== ====== ============================= When set, the optional ``ETHTOOL_A_PODL_PSE_ADMIN_CONTROL`` attribute is used @@ -1901,6 +1913,20 @@ various existing products that document power consumption in watts rather than classes. If power limit configuration based on classes is needed, the conversion can be done in user space, for example by ethtool. +When set, the optional ``ETHTOOL_A_PSE_PRIO`` attributes is used to +control the PSE priority. Allowed priority value are between zero and +the value of ``ETHTOOL_A_PSE_PRIO_MAX`` attribute. + +A lower value indicates a higher priority, meaning that a priority value +of 0 corresponds to the highest port priority. +Port priority serves two functions: + + - Power-up Order: After a reset, ports are powered up in order of their + priority from highest to lowest. Ports with higher priority + (lower values) power up first. + - Shutdown Order: When the power budget is exceeded, ports with lower + priority (higher values) are turned off first. + PSE_NTF ======= diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index c6a95224be25..8e5d067e7ddf 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -671,6 +671,8 @@ enum { ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT, ETHTOOL_A_C33_PSE_PW_LIMIT_RANGES, ETHTOOL_A_PSE_PW_D_ID, + ETHTOOL_A_PSE_PRIO_MAX, + ETHTOOL_A_PSE_PRIO, __ETHTOOL_A_PSE_CNT, ETHTOOL_A_PSE_MAX = (__ETHTOOL_A_PSE_CNT - 1) diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c index 6a978a55959e..6c536dfe52da 100644 --- a/net/ethtool/pse-pd.c +++ b/net/ethtool/pse-pd.c @@ -111,6 +111,9 @@ static int pse_reply_size(const struct ethnl_req_info *req_base, len += st->c33_pw_limit_nb_ranges * (nla_total_size(0) + nla_total_size(sizeof(u32)) * 2); + if (st->prio_max) + /* _PSE_PRIO_MAX + _PSE_PRIO */ + len += nla_total_size(sizeof(u32)) * 2; return len; } @@ -205,6 +208,11 @@ static int pse_fill_reply(struct sk_buff *skb, pse_put_pw_limit_ranges(skb, st)) return -EMSGSIZE; + if (st->prio_max && + (nla_put_u32(skb, ETHTOOL_A_PSE_PRIO_MAX, st->prio_max) || + nla_put_u32(skb, ETHTOOL_A_PSE_PRIO, st->prio))) + return -EMSGSIZE; + return 0; } @@ -226,6 +234,7 @@ const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1] = { NLA_POLICY_RANGE(NLA_U32, ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED, ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED), [ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT] = { .type = NLA_U32 }, + [ETHTOOL_A_PSE_PRIO] = { .type = NLA_U32 }, }; static int @@ -274,6 +283,15 @@ ethnl_set_pse(struct ethnl_req_info *req_info, struct genl_info *info) if (ret) return ret; + if (tb[ETHTOOL_A_PSE_PRIO]) { + unsigned int prio; + + prio = nla_get_u32(tb[ETHTOOL_A_PSE_PRIO]); + ret = pse_ethtool_set_prio(phydev->psec, info->extack, prio); + if (ret) + return ret; + } + if (tb[ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT]) { unsigned int pw_limit; -- cgit v1.2.3 From 5ae1fc4069578f50798f3372f36a3c13ee565b66 Mon Sep 17 00:00:00 2001 From: Kavita Kavita Date: Wed, 4 Jun 2025 16:27:56 +0530 Subject: wifi: cfg80211: Improve the documentation for NL80211_CMD_ASSOC_MLO_RECONF The existing documentation for the NL80211_CMD_ASSOC_MLO_RECONF does not clearly explain handling of link reconfiguration request results from the driver. Add documentation to explain that the command is used as an event to notify userspace about added links information, and that the existing NL80211_CMD_LINKS_REMOVED command is used to notify userspace about removed links information. Signed-off-by: Kavita Kavita Link: https://patch.msgid.link/20250604105757.2542-2-quic_kkavita@quicinc.com Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index e9ccf43fe3c6..e53840d009d1 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -1330,7 +1330,11 @@ * TID to Link mapping for downlink/uplink traffic. * * @NL80211_CMD_ASSOC_MLO_RECONF: For a non-AP MLD station, request to - * add/remove links to/from the association. + * add/remove links to/from the association. To indicate link + * reconfiguration request results from the driver, this command is also + * used as an event to notify userspace about the added links information. + * For notifying the removed links information, the existing + * %NL80211_CMD_LINKS_REMOVED command is used. * * @NL80211_CMD_EPCS_CFG: EPCS configuration for a station. Used by userland to * control EPCS configuration. Used to notify userland on the current state -- cgit v1.2.3 From 7c598c653ad465138ecc2fe64492633c541effef Mon Sep 17 00:00:00 2001 From: Kavita Kavita Date: Wed, 4 Jun 2025 16:27:57 +0530 Subject: wifi: cfg80211: Add support for link reconfiguration negotiation offload to driver In the case of SME-in-driver, the driver can internally choose to update the links based on the AP MLD recommendation and do link reconfiguration negotiation with AP MLD. (e.g., After the driver processing the BSS Transition Management request frame received from the AP MLD with Neighbor Report containing Multi-Link element with recommended links information chooses to do link reconfiguration negotiation with AP MLD). To support this, extend cfg80211_mlo_reconf_add_done() and NL80211_CMD_ASSOC_MLO_RECONF to indicate added links information for driver-initiated link reconfiguration requests. For removed links, the driver indicates links information using the NL80211_CMD_LINKS_REMOVED event for driver-initiated cases, the same as supplicant initiated cases. For the driver-initiated case, cfg80211 will receive link reconfiguration result asynchronously from driver so holding BSSes of the accepted add links is needed in the event path. Also, no need of unhold call for the rejected add link BSSes since there was no hold call happened previously. Once the supplicant receives the NL80211_CMD_ASSOC_MLO_RECONF event, it needs to process the information about newly added links and install per-link group keys (e.g., GTK/IGTK/BIGTK etc.). In case of the SME-in-driver, using a vendor interface etc. to notify the supplicant to initiate a link reconfiguration request and then supplicant sending command to the cfg80211 can lead to race conditions. The correct design to avoid this is that the driver indicates the cfg80211 directly with the results of the link reconfiguration negotiation. Signed-off-by: Kavita Kavita Link: https://patch.msgid.link/20250604105757.2542-3-quic_kkavita@quicinc.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 6 ++++++ include/uapi/linux/nl80211.h | 6 +++++- net/wireless/mlme.c | 10 ++++++++-- net/wireless/trace.h | 10 ++++++---- 4 files changed, 25 insertions(+), 7 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 7719a90ab4d7..47b4235eea59 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -9747,6 +9747,11 @@ void cfg80211_links_removed(struct net_device *dev, u16 link_mask); * struct cfg80211_mlo_reconf_done_data - MLO reconfiguration data * @buf: MLO Reconfiguration Response frame (header + body) * @len: length of the frame data + * @driver_initiated: Indicates whether the add links request is initiated by + * driver. This is set to true when the link reconfiguration request + * initiated by driver due to AP link recommendation requests + * (Ex: BTM (BSS Transition Management) request) handling offloaded to + * driver. * @added_links: BIT mask of links successfully added to the association * @links: per-link information indexed by link ID * @links.bss: the BSS that MLO reconfiguration was requested for, ownership of @@ -9759,6 +9764,7 @@ void cfg80211_links_removed(struct net_device *dev, u16 link_mask); struct cfg80211_mlo_reconf_done_data { const u8 *buf; size_t len; + bool driver_initiated; u16 added_links; struct { struct cfg80211_bss *bss; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index e53840d009d1..a289014abe37 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -1334,7 +1334,11 @@ * reconfiguration request results from the driver, this command is also * used as an event to notify userspace about the added links information. * For notifying the removed links information, the existing - * %NL80211_CMD_LINKS_REMOVED command is used. + * %NL80211_CMD_LINKS_REMOVED command is used. This command is also used to + * notify userspace about newly added links for the current connection in + * case of AP-initiated link recommendation requests, received via + * a BTM (BSS Transition Management) request or a link reconfig notify + * frame, where the driver handles the link recommendation offload. * * @NL80211_CMD_EPCS_CFG: EPCS configuration for a station. Used by userland to * control EPCS configuration. Used to notify userland on the current state diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c index 05d44a443518..29e1ce8aff42 100644 --- a/net/wireless/mlme.c +++ b/net/wireless/mlme.c @@ -1331,7 +1331,8 @@ void cfg80211_mlo_reconf_add_done(struct net_device *dev, lockdep_assert_wiphy(wiphy); trace_cfg80211_mlo_reconf_add_done(dev, data->added_links, - data->buf, data->len); + data->buf, data->len, + data->driver_initiated); if (WARN_ON(!wdev->valid_links)) return; @@ -1361,11 +1362,16 @@ void cfg80211_mlo_reconf_add_done(struct net_device *dev, wdev->links[link_id].client.current_bss = bss_from_pub(bss); + if (data->driver_initiated) + cfg80211_hold_bss(bss_from_pub(bss)); + memcpy(wdev->links[link_id].addr, data->links[link_id].addr, ETH_ALEN); } else { - cfg80211_unhold_bss(bss_from_pub(bss)); + if (!data->driver_initiated) + cfg80211_unhold_bss(bss_from_pub(bss)); + cfg80211_put_bss(wiphy, bss); } } diff --git a/net/wireless/trace.h b/net/wireless/trace.h index 4ed9fada4ec0..61a5eca9c513 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -4126,20 +4126,22 @@ TRACE_EVENT(cfg80211_links_removed, TRACE_EVENT(cfg80211_mlo_reconf_add_done, TP_PROTO(struct net_device *netdev, u16 link_mask, - const u8 *buf, size_t len), - TP_ARGS(netdev, link_mask, buf, len), + const u8 *buf, size_t len, bool driver_initiated), + TP_ARGS(netdev, link_mask, buf, len, driver_initiated), TP_STRUCT__entry( NETDEV_ENTRY __field(u16, link_mask) __dynamic_array(u8, buf, len) + __field(bool, driver_initiated) ), TP_fast_assign( NETDEV_ASSIGN; __entry->link_mask = link_mask; memcpy(__get_dynamic_array(buf), buf, len); + __entry->driver_initiated = driver_initiated; ), - TP_printk(NETDEV_PR_FMT ", link_mask:0x%x", - NETDEV_PR_ARG, __entry->link_mask) + TP_printk(NETDEV_PR_FMT ", link_mask:0x%x, driver_initiated:%d", + NETDEV_PR_ARG, __entry->link_mask, __entry->driver_initiated) ); TRACE_EVENT(rdev_assoc_ml_reconf, -- cgit v1.2.3 From b74947b4f6ff7c122a1bb6eb38bb7ecfbb1d3820 Mon Sep 17 00:00:00 2001 From: Roopni Devanathan Date: Sun, 15 Jun 2025 13:53:09 +0530 Subject: wifi: cfg80211/mac80211: Add support to get radio index Currently, per-radio attributes are set on per-phy basis, i.e., all the radios present in a wiphy will take attributes values sent from user. But each radio in a wiphy can get different values from userspace based on its requirement. To extend support to set per-radio attributes, add support to get radio index from userspace. Add an NL attribute - NL80211_ATTR_WIPHY_RADIO_INDEX, to get user specified radio index for which attributes should be changed. Pass this to individual drivers, so that the drivers can use this radio index to change per-radio attributes when necessary. Currently, per-radio attributes identified are: NL80211_ATTR_WIPHY_TX_POWER_LEVEL NL80211_ATTR_WIPHY_ANTENNA_TX NL80211_ATTR_WIPHY_ANTENNA_RX NL80211_ATTR_WIPHY_RETRY_SHORT NL80211_ATTR_WIPHY_RETRY_LONG NL80211_ATTR_WIPHY_FRAG_THRESHOLD NL80211_ATTR_WIPHY_RTS_THRESHOLD NL80211_ATTR_WIPHY_COVERAGE_CLASS NL80211_ATTR_TXQ_LIMIT NL80211_ATTR_TXQ_MEMORY_LIMIT NL80211_ATTR_TXQ_QUANTUM By default, the radio index is set to -1. This means the attribute should be treated as a global configuration. If the user has not specified any index, then the radio index passed to individual drivers would be -1. This would indicate that the attribute applies to all radios in that wiphy. Signed-off-by: Roopni Devanathan Link: https://patch.msgid.link/20250615082312.619639-2-quic_rdevanat@quicinc.com Signed-off-by: Johannes Berg --- drivers/net/wireless/admtek/adm8211.c | 2 +- drivers/net/wireless/ath/ar5523/ar5523.c | 5 +- drivers/net/wireless/ath/ath10k/core.c | 2 +- drivers/net/wireless/ath/ath10k/hw.c | 1 + drivers/net/wireless/ath/ath10k/hw.h | 2 +- drivers/net/wireless/ath/ath10k/mac.c | 19 ++++-- drivers/net/wireless/ath/ath11k/mac.c | 14 ++-- drivers/net/wireless/ath/ath12k/mac.c | 14 ++-- drivers/net/wireless/ath/ath5k/mac80211-ops.c | 12 ++-- drivers/net/wireless/ath/ath6kl/cfg80211.c | 7 +- drivers/net/wireless/ath/ath9k/htc_drv_main.c | 10 +-- drivers/net/wireless/ath/ath9k/main.c | 9 ++- drivers/net/wireless/ath/carl9170/main.c | 2 +- drivers/net/wireless/ath/wcn36xx/main.c | 5 +- drivers/net/wireless/ath/wil6210/cfg80211.c | 3 +- drivers/net/wireless/atmel/at76c50x-usb.c | 2 +- drivers/net/wireless/broadcom/b43/main.c | 6 +- drivers/net/wireless/broadcom/b43legacy/main.c | 2 +- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 8 ++- .../broadcom/brcm80211/brcmsmac/mac80211_if.c | 3 +- drivers/net/wireless/intel/iwlegacy/common.c | 2 +- drivers/net/wireless/intel/iwlegacy/common.h | 2 +- drivers/net/wireless/intel/iwlwifi/dvm/agn.h | 2 +- drivers/net/wireless/intel/iwlwifi/dvm/rxon.c | 2 +- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 6 +- drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c | 9 ++- drivers/net/wireless/intel/iwlwifi/mvm/mvm.h | 12 ++-- drivers/net/wireless/intersil/p54/main.c | 3 +- drivers/net/wireless/marvell/libertas_tf/main.c | 2 +- drivers/net/wireless/marvell/mwifiex/cfg80211.c | 11 ++- drivers/net/wireless/marvell/mwl8k.c | 12 ++-- drivers/net/wireless/mediatek/mt76/mac80211.c | 3 +- drivers/net/wireless/mediatek/mt76/mt76.h | 3 +- drivers/net/wireless/mediatek/mt76/mt7603/main.c | 5 +- drivers/net/wireless/mediatek/mt76/mt7615/main.c | 11 +-- drivers/net/wireless/mediatek/mt76/mt76x0/main.c | 2 +- drivers/net/wireless/mediatek/mt76/mt76x0/mt76x0.h | 2 +- drivers/net/wireless/mediatek/mt76/mt76x02.h | 4 +- drivers/net/wireless/mediatek/mt76/mt76x02_util.c | 4 +- .../net/wireless/mediatek/mt76/mt76x2/pci_main.c | 6 +- .../net/wireless/mediatek/mt76/mt76x2/usb_main.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 13 ++-- drivers/net/wireless/mediatek/mt76/mt7921/main.c | 8 ++- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 8 ++- drivers/net/wireless/mediatek/mt76/mt792x.h | 3 +- drivers/net/wireless/mediatek/mt76/mt792x_core.c | 3 +- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 11 +-- drivers/net/wireless/mediatek/mt7601u/main.c | 5 +- drivers/net/wireless/microchip/wilc1000/cfg80211.c | 7 +- drivers/net/wireless/purelifi/plfxlc/mac.c | 5 +- drivers/net/wireless/quantenna/qtnfmac/cfg80211.c | 8 ++- drivers/net/wireless/ralink/rt2x00/rt2800lib.c | 2 +- drivers/net/wireless/ralink/rt2x00/rt2800lib.h | 3 +- drivers/net/wireless/ralink/rt2x00/rt2x00.h | 8 ++- drivers/net/wireless/ralink/rt2x00/rt2x00mac.c | 8 ++- drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c | 2 +- drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c | 2 +- drivers/net/wireless/realtek/rtl8xxxu/core.c | 8 ++- drivers/net/wireless/realtek/rtlwifi/core.c | 2 +- drivers/net/wireless/realtek/rtw88/mac80211.c | 9 ++- drivers/net/wireless/realtek/rtw88/main.h | 2 +- drivers/net/wireless/realtek/rtw88/rtw8822b.c | 1 + drivers/net/wireless/realtek/rtw88/rtw8822c.c | 1 + drivers/net/wireless/realtek/rtw89/mac80211.c | 10 +-- drivers/net/wireless/rsi/rsi_91x_mac80211.c | 9 ++- drivers/net/wireless/silabs/wfx/sta.c | 4 +- drivers/net/wireless/silabs/wfx/sta.h | 4 +- drivers/net/wireless/st/cw1200/sta.c | 5 +- drivers/net/wireless/st/cw1200/sta.h | 5 +- drivers/net/wireless/ti/wl1251/main.c | 5 +- drivers/net/wireless/ti/wlcore/main.c | 8 ++- drivers/net/wireless/virtual/mac80211_hwsim.c | 6 +- drivers/net/wireless/zydas/zd1211rw/zd_mac.c | 2 +- drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c | 6 +- include/net/cfg80211.h | 12 ++-- include/net/mac80211.h | 17 +++-- include/uapi/linux/nl80211.h | 10 +++ net/mac80211/cfg.c | 30 +++++--- net/mac80211/chan.c | 2 +- net/mac80211/driver-ops.h | 36 +++++----- net/mac80211/ieee80211_i.h | 5 +- net/mac80211/iface.c | 6 +- net/mac80211/main.c | 9 +-- net/mac80211/mlme.c | 12 ++-- net/mac80211/offchannel.c | 2 +- net/mac80211/pm.c | 2 +- net/mac80211/trace.h | 78 ++++++++++++++++----- net/mac80211/tx.c | 4 +- net/mac80211/util.c | 16 ++--- net/wireless/nl80211.c | 26 +++++-- net/wireless/rdev-ops.h | 39 ++++++----- net/wireless/trace.h | 79 +++++++++++++++------- net/wireless/wext-compat.c | 10 +-- 93 files changed, 520 insertions(+), 291 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/wireless/admtek/adm8211.c b/drivers/net/wireless/admtek/adm8211.c index a2d87c3ad196..e94a6b180314 100644 --- a/drivers/net/wireless/admtek/adm8211.c +++ b/drivers/net/wireless/admtek/adm8211.c @@ -1293,7 +1293,7 @@ static void adm8211_set_bssid(struct ieee80211_hw *dev, const u8 *bssid) ADM8211_CSR_WRITE(ABDA1, reg); } -static int adm8211_config(struct ieee80211_hw *dev, u32 changed) +static int adm8211_config(struct ieee80211_hw *dev, int radio_idx, u32 changed) { struct adm8211_priv *priv = dev->priv; struct ieee80211_conf *conf = &dev->conf; diff --git a/drivers/net/wireless/ath/ar5523/ar5523.c b/drivers/net/wireless/ath/ar5523/ar5523.c index 343c9de2749c..1230e6278f23 100644 --- a/drivers/net/wireless/ath/ar5523/ar5523.c +++ b/drivers/net/wireless/ath/ar5523/ar5523.c @@ -1083,7 +1083,8 @@ static void ar5523_stop(struct ieee80211_hw *hw, bool suspend) mutex_unlock(&ar->mutex); } -static int ar5523_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int ar5523_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { struct ar5523 *ar = hw->priv; int ret; @@ -1137,7 +1138,7 @@ static void ar5523_remove_interface(struct ieee80211_hw *hw, ar->vif = NULL; } -static int ar5523_hwconfig(struct ieee80211_hw *hw, u32 changed) +static int ar5523_hwconfig(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct ar5523 *ar = hw->priv; diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c index fe3a8f4a1cc1..52163c2bfe7a 100644 --- a/drivers/net/wireless/ath/ath10k/core.c +++ b/drivers/net/wireless/ath/ath10k/core.c @@ -2606,7 +2606,7 @@ static void ath10k_core_set_coverage_class_work(struct work_struct *work) set_coverage_class_work); if (ar->hw_params.hw_ops->set_coverage_class) - ar->hw_params.hw_ops->set_coverage_class(ar, -1); + ar->hw_params.hw_ops->set_coverage_class(ar, -1, -1); } static int ath10k_core_init_firmware_features(struct ath10k *ar) diff --git a/drivers/net/wireless/ath/ath10k/hw.c b/drivers/net/wireless/ath/ath10k/hw.c index 84b35a22fc23..59b6cebfdd8f 100644 --- a/drivers/net/wireless/ath/ath10k/hw.c +++ b/drivers/net/wireless/ath/ath10k/hw.c @@ -590,6 +590,7 @@ void ath10k_hw_fill_survey_time(struct ath10k *ar, struct survey_info *survey, * function monitors and modifies the corresponding MAC registers. */ static void ath10k_hw_qca988x_set_coverage_class(struct ath10k *ar, + int radio_idx, s16 value) { u32 slottime_reg; diff --git a/drivers/net/wireless/ath/ath10k/hw.h b/drivers/net/wireless/ath/ath10k/hw.h index 7ffa1fbe2874..fec56b916497 100644 --- a/drivers/net/wireless/ath/ath10k/hw.h +++ b/drivers/net/wireless/ath/ath10k/hw.h @@ -646,7 +646,7 @@ struct htt_rx_ring_rx_desc_offsets; /* Defines needed for Rx descriptor abstraction */ struct ath10k_hw_ops { - void (*set_coverage_class)(struct ath10k *ar, s16 value); + void (*set_coverage_class)(struct ath10k *ar, int radio_idx, s16 value); int (*enable_pll_clk)(struct ath10k *ar); int (*tx_data_rssi_pad_bytes)(struct htt_resp *htt); int (*is_rssi_enable)(struct htt_resp *resp); diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c index 07fe05384cdf..590d7a8dd399 100644 --- a/drivers/net/wireless/ath/ath10k/mac.c +++ b/drivers/net/wireless/ath/ath10k/mac.c @@ -4820,7 +4820,8 @@ void ath10k_halt(struct ath10k *ar) spin_unlock_bh(&ar->data_lock); } -static int ath10k_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +static int ath10k_get_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 *tx_ant, u32 *rx_ant) { struct ath10k *ar = hw->priv; @@ -5067,7 +5068,8 @@ static int __ath10k_set_antenna(struct ath10k *ar, u32 tx_ant, u32 rx_ant) return 0; } -static int ath10k_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +static int ath10k_set_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct ath10k *ar = hw->priv; int ret; @@ -5437,7 +5439,7 @@ static int ath10k_config_ps(struct ath10k *ar) return ret; } -static int ath10k_config(struct ieee80211_hw *hw, u32 changed) +static int ath10k_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct ath10k *ar = hw->priv; struct ieee80211_conf *conf = &hw->conf; @@ -6336,7 +6338,8 @@ static void ath10k_bss_info_changed(struct ieee80211_hw *hw, mutex_unlock(&ar->conf_mutex); } -static void ath10k_mac_op_set_coverage_class(struct ieee80211_hw *hw, s16 value) +static void ath10k_mac_op_set_coverage_class(struct ieee80211_hw *hw, int radio_idx, + s16 value) { struct ath10k *ar = hw->priv; @@ -6347,7 +6350,7 @@ static void ath10k_mac_op_set_coverage_class(struct ieee80211_hw *hw, s16 value) WARN_ON_ONCE(1); return; } - ar->hw_params.hw_ops->set_coverage_class(ar, value); + ar->hw_params.hw_ops->set_coverage_class(ar, -1, value); } struct ath10k_mac_tdls_iter_data { @@ -8035,7 +8038,8 @@ static int ath10k_cancel_remain_on_channel(struct ieee80211_hw *hw, * in ath10k, but device-specific in mac80211. */ -static int ath10k_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int ath10k_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { struct ath10k *ar = hw->priv; struct ath10k_vif *arvif; @@ -8058,7 +8062,8 @@ static int ath10k_set_rts_threshold(struct ieee80211_hw *hw, u32 value) return ret; } -static int ath10k_mac_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value) +static int ath10k_mac_op_set_frag_threshold(struct ieee80211_hw *hw, + int radio_idx, u32 value) { /* Even though there's a WMI enum for fragmentation threshold no known * firmware actually implements it. Moreover it is not possible to rely diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c index 13301ca317a5..758ef6f26432 100644 --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -1283,7 +1283,7 @@ static int ath11k_mac_config_ps(struct ath11k *ar) return ret; } -static int ath11k_mac_op_config(struct ieee80211_hw *hw, u32 changed) +static int ath11k_mac_op_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct ath11k *ar = hw->priv; struct ieee80211_conf *conf = &hw->conf; @@ -7044,7 +7044,8 @@ static void ath11k_mac_op_configure_filter(struct ieee80211_hw *hw, mutex_unlock(&ar->conf_mutex); } -static int ath11k_mac_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +static int ath11k_mac_op_get_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 *tx_ant, u32 *rx_ant) { struct ath11k *ar = hw->priv; @@ -7058,7 +7059,8 @@ static int ath11k_mac_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 * return 0; } -static int ath11k_mac_op_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +static int ath11k_mac_op_set_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct ath11k *ar = hw->priv; int ret; @@ -8182,7 +8184,8 @@ ath11k_set_vdev_param_to_all_vifs(struct ath11k *ar, int param, u32 value) /* mac80211 stores device specific RTS/Fragmentation threshold value, * this is set interface specific to firmware from ath11k driver */ -static int ath11k_mac_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int ath11k_mac_op_set_rts_threshold(struct ieee80211_hw *hw, + int radio_idx, u32 value) { struct ath11k *ar = hw->priv; int param_id = WMI_VDEV_PARAM_RTS_THRESHOLD; @@ -8190,7 +8193,8 @@ static int ath11k_mac_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value) return ath11k_set_vdev_param_to_all_vifs(ar, param_id, value); } -static int ath11k_mac_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value) +static int ath11k_mac_op_set_frag_threshold(struct ieee80211_hw *hw, + int radio_idx, u32 value) { /* Even though there's a WMI vdev param for fragmentation threshold no * known firmware actually implements it. Moreover it is not possible to diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 59ec422992d3..81c6b80fa890 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1392,7 +1392,7 @@ err: return ret; } -static int ath12k_mac_op_config(struct ieee80211_hw *hw, u32 changed) +static int ath12k_mac_op_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { return 0; } @@ -9354,7 +9354,8 @@ static void ath12k_mac_op_configure_filter(struct ieee80211_hw *hw, ar->filter_flags = *total_flags; } -static int ath12k_mac_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +static int ath12k_mac_op_get_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 *tx_ant, u32 *rx_ant) { struct ath12k_hw *ah = ath12k_hw_to_ah(hw); int antennas_rx = 0, antennas_tx = 0; @@ -9374,7 +9375,8 @@ static int ath12k_mac_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 * return 0; } -static int ath12k_mac_op_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +static int ath12k_mac_op_set_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct ath12k_hw *ah = ath12k_hw_to_ah(hw); struct ath12k *ar; @@ -10735,7 +10737,8 @@ ath12k_set_vdev_param_to_all_vifs(struct ath12k *ar, int param, u32 value) /* mac80211 stores device specific RTS/Fragmentation threshold value, * this is set interface specific to firmware from ath12k driver */ -static int ath12k_mac_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int ath12k_mac_op_set_rts_threshold(struct ieee80211_hw *hw, + int radio_idx, u32 value) { struct ath12k_hw *ah = ath12k_hw_to_ah(hw); struct ath12k *ar; @@ -10760,7 +10763,8 @@ static int ath12k_mac_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value) return ret; } -static int ath12k_mac_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value) +static int ath12k_mac_op_set_frag_threshold(struct ieee80211_hw *hw, + int radio_idx, u32 value) { /* Even though there's a WMI vdev param for fragmentation threshold no * known firmware actually implements it. Moreover it is not possible to diff --git a/drivers/net/wireless/ath/ath5k/mac80211-ops.c b/drivers/net/wireless/ath/ath5k/mac80211-ops.c index d81b2ad0b095..eca8145d3874 100644 --- a/drivers/net/wireless/ath/ath5k/mac80211-ops.c +++ b/drivers/net/wireless/ath/ath5k/mac80211-ops.c @@ -192,7 +192,7 @@ ath5k_remove_interface(struct ieee80211_hw *hw, * TODO: Phy disable/diversity etc */ static int -ath5k_config(struct ieee80211_hw *hw, u32 changed) +ath5k_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct ath5k_hw *ah = hw->priv; struct ieee80211_conf *conf = &hw->conf; @@ -686,6 +686,7 @@ ath5k_get_survey(struct ieee80211_hw *hw, int idx, struct survey_info *survey) * ath5k_set_coverage_class - Set IEEE 802.11 coverage class * * @hw: struct ieee80211_hw pointer + * @radio_idx: Radio index * @coverage_class: IEEE 802.11 coverage class number * * Mac80211 callback. Sets slot time, ACK timeout and CTS timeout for given @@ -693,7 +694,8 @@ ath5k_get_survey(struct ieee80211_hw *hw, int idx, struct survey_info *survey) * reset. */ static void -ath5k_set_coverage_class(struct ieee80211_hw *hw, s16 coverage_class) +ath5k_set_coverage_class(struct ieee80211_hw *hw, int radio_idx, + s16 coverage_class) { struct ath5k_hw *ah = hw->priv; @@ -704,7 +706,8 @@ ath5k_set_coverage_class(struct ieee80211_hw *hw, s16 coverage_class) static int -ath5k_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +ath5k_set_antenna(struct ieee80211_hw *hw, int radio_idx, u32 tx_ant, + u32 rx_ant) { struct ath5k_hw *ah = hw->priv; @@ -721,7 +724,8 @@ ath5k_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) static int -ath5k_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +ath5k_get_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 *tx_ant, u32 *rx_ant) { struct ath5k_hw *ah = hw->priv; diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.c b/drivers/net/wireless/ath/ath6kl/cfg80211.c index 8c2e8081112e..88f0197fc041 100644 --- a/drivers/net/wireless/ath/ath6kl/cfg80211.c +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.c @@ -1376,7 +1376,8 @@ void ath6kl_cfg80211_tkip_micerr_event(struct ath6kl_vif *vif, u8 keyid, GFP_KERNEL); } -static int ath6kl_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +static int ath6kl_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, + u32 changed) { struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy); struct ath6kl_vif *vif; @@ -1405,6 +1406,7 @@ static int ath6kl_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) static int ath6kl_cfg80211_set_txpower(struct wiphy *wiphy, struct wireless_dev *wdev, + int radio_idx, enum nl80211_tx_power_setting type, int mbm) { @@ -1441,6 +1443,7 @@ static int ath6kl_cfg80211_set_txpower(struct wiphy *wiphy, static int ath6kl_cfg80211_get_txpower(struct wiphy *wiphy, struct wireless_dev *wdev, + int radio_idx, unsigned int link_id, int *dbm) { @@ -3242,7 +3245,7 @@ static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, wait, buf, len, no_cck); } -static int ath6kl_get_antenna(struct wiphy *wiphy, +static int ath6kl_get_antenna(struct wiphy *wiphy, int radio_idx, u32 *tx_ant, u32 *rx_ant) { struct ath6kl *ar = wiphy_priv(wiphy); diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_main.c b/drivers/net/wireless/ath/ath9k/htc_drv_main.c index 19600018e562..0d6272ac0dac 100644 --- a/drivers/net/wireless/ath/ath9k/htc_drv_main.c +++ b/drivers/net/wireless/ath/ath9k/htc_drv_main.c @@ -1172,7 +1172,7 @@ static void ath9k_htc_remove_interface(struct ieee80211_hw *hw, mutex_unlock(&priv->mutex); } -static int ath9k_htc_config(struct ieee80211_hw *hw, u32 changed) +static int ath9k_htc_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct ath9k_htc_priv *priv = hw->priv; struct ath_common *common = ath9k_hw_common(priv->ah); @@ -1737,12 +1737,14 @@ static void ath9k_htc_sw_scan_complete(struct ieee80211_hw *hw, mutex_unlock(&priv->mutex); } -static int ath9k_htc_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int ath9k_htc_set_rts_threshold(struct ieee80211_hw *hw, + int radio_idx, u32 value) { return 0; } static void ath9k_htc_set_coverage_class(struct ieee80211_hw *hw, + int radio_idx, s16 coverage_class) { struct ath9k_htc_priv *priv = hw->priv; @@ -1841,8 +1843,8 @@ struct base_eep_header *ath9k_htc_get_eeprom_base(struct ath9k_htc_priv *priv) } -static int ath9k_htc_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, - u32 *rx_ant) +static int ath9k_htc_get_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 *tx_ant, u32 *rx_ant) { struct ath9k_htc_priv *priv = hw->priv; struct base_eep_header *pBase = ath9k_htc_get_eeprom_base(priv); diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c index c56f4f3b8990..740a6fc7b067 100644 --- a/drivers/net/wireless/ath/ath9k/main.c +++ b/drivers/net/wireless/ath/ath9k/main.c @@ -1484,7 +1484,7 @@ static void ath9k_disable_ps(struct ath_softc *sc) ath_dbg(common, PS, "PowerSave disabled\n"); } -static int ath9k_config(struct ieee80211_hw *hw, u32 changed) +static int ath9k_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct ath_softc *sc = hw->priv; struct ath_hw *ah = sc->sc_ah; @@ -2114,6 +2114,7 @@ static void ath9k_enable_dynack(struct ath_softc *sc) } static void ath9k_set_coverage_class(struct ieee80211_hw *hw, + int radio_idx, s16 coverage_class) { struct ath_softc *sc = hw->priv; @@ -2338,7 +2339,8 @@ static bool validate_antenna_mask(struct ath_hw *ah, u32 val) } } -static int ath9k_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +static int ath9k_set_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct ath_softc *sc = hw->priv; struct ath_hw *ah = sc->sc_ah; @@ -2367,7 +2369,8 @@ static int ath9k_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) return 0; } -static int ath9k_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +static int ath9k_get_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 *tx_ant, u32 *rx_ant) { struct ath_softc *sc = hw->priv; diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c index 755c068e4197..a7a9345f3483 100644 --- a/drivers/net/wireless/ath/carl9170/main.c +++ b/drivers/net/wireless/ath/carl9170/main.c @@ -890,7 +890,7 @@ static void carl9170_stat_work(struct work_struct *work) round_jiffies(msecs_to_jiffies(CARL9170_STAT_WORK))); } -static int carl9170_op_config(struct ieee80211_hw *hw, u32 changed) +static int carl9170_op_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct ar9170 *ar = hw->priv; int err = 0; diff --git a/drivers/net/wireless/ath/wcn36xx/main.c b/drivers/net/wireless/ath/wcn36xx/main.c index 94d08d6ae1a3..02a525645bfa 100644 --- a/drivers/net/wireless/ath/wcn36xx/main.c +++ b/drivers/net/wireless/ath/wcn36xx/main.c @@ -361,7 +361,7 @@ static void wcn36xx_change_opchannel(struct wcn36xx *wcn, int ch) return; } -static int wcn36xx_config(struct ieee80211_hw *hw, u32 changed) +static int wcn36xx_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct wcn36xx *wcn = hw->priv; int ret; @@ -965,7 +965,8 @@ out: } /* this is required when using IEEE80211_HW_HAS_RATE_CONTROL */ -static int wcn36xx_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int wcn36xx_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { struct wcn36xx *wcn = hw->priv; wcn36xx_dbg(WCN36XX_DBG_MAC, "mac set RTS threshold %d\n", value); diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index 5473c01cbe66..7703a0933a14 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -1408,7 +1408,8 @@ static int wil_cfg80211_disconnect(struct wiphy *wiphy, return rc; } -static int wil_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +static int wil_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, + u32 changed) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); int rc; diff --git a/drivers/net/wireless/atmel/at76c50x-usb.c b/drivers/net/wireless/atmel/at76c50x-usb.c index 6842c2b02b39..aa683eacaf38 100644 --- a/drivers/net/wireless/atmel/at76c50x-usb.c +++ b/drivers/net/wireless/atmel/at76c50x-usb.c @@ -2002,7 +2002,7 @@ exit: return 0; } -static int at76_config(struct ieee80211_hw *hw, u32 changed) +static int at76_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct at76_priv *priv = hw->priv; diff --git a/drivers/net/wireless/broadcom/b43/main.c b/drivers/net/wireless/broadcom/b43/main.c index 7529afd24aed..f1a77c4c445f 100644 --- a/drivers/net/wireless/broadcom/b43/main.c +++ b/drivers/net/wireless/broadcom/b43/main.c @@ -3975,7 +3975,7 @@ static void b43_set_retry_limits(struct b43_wldev *dev, long_retry); } -static int b43_op_config(struct ieee80211_hw *hw, u32 changed) +static int b43_op_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct b43_wl *wl = hw_to_b43_wl(hw); struct b43_wldev *dev = wl->current_dev; @@ -5073,7 +5073,7 @@ static int b43_op_start(struct ieee80211_hw *hw) * may hang the system. */ if (!err) - b43_op_config(hw, ~0); + b43_op_config(hw, -1, ~0); return err; } @@ -5248,7 +5248,7 @@ out: } /* reload configuration */ - b43_op_config(wl->hw, ~0); + b43_op_config(wl->hw, -1, ~0); if (wl->vif) b43_op_bss_info_changed(wl->hw, wl->vif, &wl->vif->bss_conf, ~0); diff --git a/drivers/net/wireless/broadcom/b43legacy/main.c b/drivers/net/wireless/broadcom/b43legacy/main.c index 2370a2e6a2e3..aada342e0b80 100644 --- a/drivers/net/wireless/broadcom/b43legacy/main.c +++ b/drivers/net/wireless/broadcom/b43legacy/main.c @@ -2662,7 +2662,7 @@ static void b43legacy_set_retry_limits(struct b43legacy_wldev *dev, b43legacy_shm_write16(dev, B43legacy_SHM_WIRELESS, 0x0007, long_retry); } -static int b43legacy_op_dev_config(struct ieee80211_hw *hw, +static int b43legacy_op_dev_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 5a0b252dfeaf..40a9a8177de6 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -1637,7 +1637,8 @@ static s32 brcmf_set_retry(struct net_device *ndev, u32 retry, bool l) return err; } -static s32 brcmf_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +static s32 brcmf_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, + u32 changed) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = cfg_to_ndev(cfg); @@ -2645,7 +2646,8 @@ brcmf_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *ndev, static s32 brcmf_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, - enum nl80211_tx_power_setting type, s32 mbm) + int radio_idx, enum nl80211_tx_power_setting type, + s32 mbm) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = cfg_to_ndev(cfg); @@ -2696,7 +2698,7 @@ done: static s32 brcmf_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, - unsigned int link_id, s32 *dbm) + int radio_idx, unsigned int link_id, s32 *dbm) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_cfg80211_vif *vif = wdev_to_vif(wdev); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c index 1c3d29dca424..8ab452cf48c4 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c @@ -525,7 +525,8 @@ brcms_ops_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) spin_unlock_bh(&wl->lock); } -static int brcms_ops_config(struct ieee80211_hw *hw, u32 changed) +static int brcms_ops_config(struct ieee80211_hw *hw, int radio_idx, + u32 changed) { struct ieee80211_conf *conf = &hw->conf; struct brcms_info *wl = hw->priv; diff --git a/drivers/net/wireless/intel/iwlegacy/common.c b/drivers/net/wireless/intel/iwlegacy/common.c index 9a86688aea67..b7bd3ec4cc50 100644 --- a/drivers/net/wireless/intel/iwlegacy/common.c +++ b/drivers/net/wireless/intel/iwlegacy/common.c @@ -4990,7 +4990,7 @@ il_update_qos(struct il_priv *il) * il_mac_config - mac80211 config callback */ int -il_mac_config(struct ieee80211_hw *hw, u32 changed) +il_mac_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct il_priv *il = hw->priv; const struct il_channel_info *ch_info; diff --git a/drivers/net/wireless/intel/iwlegacy/common.h b/drivers/net/wireless/intel/iwlegacy/common.h index 52610f5e57a3..4c9836ab11dd 100644 --- a/drivers/net/wireless/intel/iwlegacy/common.h +++ b/drivers/net/wireless/intel/iwlegacy/common.h @@ -1956,7 +1956,7 @@ il_get_hw_mode(struct il_priv *il, enum nl80211_band band) } /* mac80211 handlers */ -int il_mac_config(struct ieee80211_hw *hw, u32 changed); +int il_mac_config(struct ieee80211_hw *hw, int radio_idx, u32 changed); void il_mac_reset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif); void il_mac_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *bss_conf, u64 changes); diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/agn.h b/drivers/net/wireless/intel/iwlwifi/dvm/agn.h index 1ebc7effcc2a..b39bf401567f 100644 --- a/drivers/net/wireless/intel/iwlwifi/dvm/agn.h +++ b/drivers/net/wireless/intel/iwlwifi/dvm/agn.h @@ -88,7 +88,7 @@ void iwl_connection_init_rx_config(struct iwl_priv *priv, int iwlagn_set_pan_params(struct iwl_priv *priv); int iwlagn_commit_rxon(struct iwl_priv *priv, struct iwl_rxon_context *ctx); void iwlagn_set_rxon_chain(struct iwl_priv *priv, struct iwl_rxon_context *ctx); -int iwlagn_mac_config(struct ieee80211_hw *hw, u32 changed); +int iwlagn_mac_config(struct ieee80211_hw *hw, int radio_idx, u32 changed); void iwlagn_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *bss_conf, diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/rxon.c b/drivers/net/wireless/intel/iwlwifi/dvm/rxon.c index 2d3c1627f283..e08e44cae434 100644 --- a/drivers/net/wireless/intel/iwlwifi/dvm/rxon.c +++ b/drivers/net/wireless/intel/iwlwifi/dvm/rxon.c @@ -1149,7 +1149,7 @@ void iwlagn_config_ht40(struct ieee80211_conf *conf, } } -int iwlagn_mac_config(struct ieee80211_hw *hw, u32 changed) +int iwlagn_mac_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); struct iwl_rxon_context *ctx; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 4ba050397632..76e7e3fa2d13 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -574,7 +574,8 @@ void iwl_mld_mac80211_stop(struct ieee80211_hw *hw, bool suspend) } static -int iwl_mld_mac80211_config(struct ieee80211_hw *hw, u32 changed) +int iwl_mld_mac80211_config(struct ieee80211_hw *hw, int radio_idx, + u32 changed) { return 0; } @@ -1102,7 +1103,8 @@ void iwl_mld_unassign_vif_chanctx(struct ieee80211_hw *hw, } static -int iwl_mld_mac80211_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +int iwl_mld_mac80211_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { return 0; } diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index 956b491ae5a4..619d822efa5b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -298,7 +298,8 @@ static const struct wiphy_iftype_ext_capab add_iftypes_ext_capa[] = { }, }; -int iwl_mvm_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +int iwl_mvm_op_get_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 *tx_ant, u32 *rx_ant) { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); *tx_ant = iwl_mvm_get_valid_tx_ant(mvm); @@ -306,7 +307,8 @@ int iwl_mvm_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) return 0; } -int iwl_mvm_op_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +int iwl_mvm_op_set_antenna(struct ieee80211_hw *hw, int radio_idx, u32 tx_ant, + u32 rx_ant) { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); @@ -4249,7 +4251,8 @@ int iwl_mvm_mac_sta_state_common(struct ieee80211_hw *hw, return ret; } -int iwl_mvm_mac_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +int iwl_mvm_mac_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index a4f412e750d0..5c8eaf1eacff 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -2866,13 +2866,16 @@ void iwl_mvm_mac_wake_tx_queue(struct ieee80211_hw *hw, int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_ampdu_params *params); -int iwl_mvm_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant); -int iwl_mvm_op_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant); +int iwl_mvm_op_get_antenna(struct ieee80211_hw *hw, int radio_idx, u32 *tx_ant, + u32 *rx_ant); +int iwl_mvm_op_set_antenna(struct ieee80211_hw *hw, int radio_idx, u32 tx_ant, + u32 rx_ant); int iwl_mvm_mac_start(struct ieee80211_hw *hw); void iwl_mvm_mac_reconfig_complete(struct ieee80211_hw *hw, enum ieee80211_reconfig_type reconfig_type); void iwl_mvm_mac_stop(struct ieee80211_hw *hw, bool suspend); -static inline int iwl_mvm_mac_config(struct ieee80211_hw *hw, u32 changed) +static inline int iwl_mvm_mac_config(struct ieee80211_hw *hw, int radio_idx, + u32 changed) { return 0; } @@ -2905,7 +2908,8 @@ iwl_mvm_mac_release_buffered_frames(struct ieee80211_hw *hw, int num_frames, enum ieee80211_frame_release_type reason, bool more_data); -int iwl_mvm_mac_set_rts_threshold(struct ieee80211_hw *hw, u32 value); +int iwl_mvm_mac_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value); void iwl_mvm_sta_rc_update(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_link_sta *link_sta, u32 changed); void iwl_mvm_mac_mgd_prepare_tx(struct ieee80211_hw *hw, diff --git a/drivers/net/wireless/intersil/p54/main.c b/drivers/net/wireless/intersil/p54/main.c index 42111bb53f58..2ec3655f1a9c 100644 --- a/drivers/net/wireless/intersil/p54/main.c +++ b/drivers/net/wireless/intersil/p54/main.c @@ -313,7 +313,7 @@ static void p54_reset_stats(struct p54_common *priv) priv->survey_raw.tx = 0; } -static int p54_config(struct ieee80211_hw *dev, u32 changed) +static int p54_config(struct ieee80211_hw *dev, int radio_idx, u32 changed) { int ret = 0; struct p54_common *priv = dev->priv; @@ -692,6 +692,7 @@ static void p54_flush(struct ieee80211_hw *dev, struct ieee80211_vif *vif, } static void p54_set_coverage_class(struct ieee80211_hw *dev, + int radio_idx, s16 coverage_class) { struct p54_common *priv = dev->priv; diff --git a/drivers/net/wireless/marvell/libertas_tf/main.c b/drivers/net/wireless/marvell/libertas_tf/main.c index 50c0f6179e2d..d1067874428f 100644 --- a/drivers/net/wireless/marvell/libertas_tf/main.c +++ b/drivers/net/wireless/marvell/libertas_tf/main.c @@ -337,7 +337,7 @@ static void lbtf_op_remove_interface(struct ieee80211_hw *hw, lbtf_deb_leave(LBTF_DEB_MACOPS); } -static int lbtf_op_config(struct ieee80211_hw *hw, u32 changed) +static int lbtf_op_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct lbtf_private *priv = hw->priv; struct ieee80211_conf *conf = &hw->conf; diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index 60c12328c2f3..286378770e9e 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -375,6 +375,7 @@ mwifiex_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy, static int mwifiex_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + int radio_idx, enum nl80211_tx_power_setting type, int mbm) { @@ -410,6 +411,7 @@ mwifiex_cfg80211_set_tx_power(struct wiphy *wiphy, static int mwifiex_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + int radio_idx, unsigned int link_id, int *dbm) { struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); @@ -737,7 +739,8 @@ mwifiex_set_rts(struct mwifiex_private *priv, u32 rts_thr) * Fragmentation threshold of the driver. */ static int -mwifiex_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +mwifiex_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, + u32 changed) { struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); struct mwifiex_private *priv; @@ -1939,7 +1942,8 @@ mwifiex_cfg80211_del_station(struct wiphy *wiphy, struct net_device *dev, } static int -mwifiex_cfg80211_set_antenna(struct wiphy *wiphy, u32 tx_ant, u32 rx_ant) +mwifiex_cfg80211_set_antenna(struct wiphy *wiphy, int radio_idx, u32 tx_ant, + u32 rx_ant) { struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); struct mwifiex_private *priv = mwifiex_get_priv(adapter, @@ -2002,7 +2006,8 @@ mwifiex_cfg80211_set_antenna(struct wiphy *wiphy, u32 tx_ant, u32 rx_ant) } static int -mwifiex_cfg80211_get_antenna(struct wiphy *wiphy, u32 *tx_ant, u32 *rx_ant) +mwifiex_cfg80211_get_antenna(struct wiphy *wiphy, int radio_idx, u32 *tx_ant, + u32 *rx_ant) { struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); struct mwifiex_private *priv = mwifiex_get_priv(adapter, diff --git a/drivers/net/wireless/marvell/mwl8k.c b/drivers/net/wireless/marvell/mwl8k.c index bab9ef37a1ab..bc34a025acd6 100644 --- a/drivers/net/wireless/marvell/mwl8k.c +++ b/drivers/net/wireless/marvell/mwl8k.c @@ -3369,7 +3369,8 @@ struct mwl8k_cmd_set_rts_threshold { } __packed; static int -mwl8k_cmd_set_rts_threshold(struct ieee80211_hw *hw, int rts_thresh) +mwl8k_cmd_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + int rts_thresh) { struct mwl8k_cmd_set_rts_threshold *cmd; int rc; @@ -4955,7 +4956,7 @@ fail: wiphy_err(hw->wiphy, "Firmware restart failed\n"); } -static int mwl8k_config(struct ieee80211_hw *hw, u32 changed) +static int mwl8k_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct ieee80211_conf *conf = &hw->conf; struct mwl8k_priv *priv = hw->priv; @@ -5321,9 +5322,10 @@ static void mwl8k_configure_filter(struct ieee80211_hw *hw, mwl8k_fw_unlock(hw); } -static int mwl8k_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int mwl8k_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { - return mwl8k_cmd_set_rts_threshold(hw, value); + return mwl8k_cmd_set_rts_threshold(hw, radio_idx, value); } static int mwl8k_sta_remove(struct ieee80211_hw *hw, @@ -6056,7 +6058,7 @@ static int mwl8k_reload_firmware(struct ieee80211_hw *hw, char *fw_image) if (rc) goto fail; - rc = mwl8k_config(hw, ~0); + rc = mwl8k_config(hw, -1, ~0); if (rc) goto fail; diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 45c8db939d55..3afe4c4cd7bb 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -1892,7 +1892,8 @@ void mt76_sw_scan_complete(struct ieee80211_hw *hw, struct ieee80211_vif *vif) } EXPORT_SYMBOL_GPL(mt76_sw_scan_complete); -int mt76_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +int mt76_get_antenna(struct ieee80211_hw *hw, int radio_idx, u32 *tx_ant, + u32 *rx_ant) { struct mt76_phy *phy = hw->priv; struct mt76_dev *dev = phy->dev; diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 5f8d81cda6cd..14927a92f9d1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1513,7 +1513,8 @@ int mt76_get_sar_power(struct mt76_phy *phy, void mt76_csa_check(struct mt76_dev *dev); void mt76_csa_finish(struct mt76_dev *dev); -int mt76_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant); +int mt76_get_antenna(struct ieee80211_hw *hw, int radio_idx, u32 *tx_ant, + u32 *rx_ant); int mt76_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool set); void mt76_insert_ccmp_hdr(struct sk_buff *skb, u8 key_id); int mt76_get_rate(struct mt76_dev *dev, diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/main.c b/drivers/net/wireless/mediatek/mt76/mt7603/main.c index 3e8b1ec76169..0d7c84941cd0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/main.c @@ -216,7 +216,7 @@ static int mt7603_set_sar_specs(struct ieee80211_hw *hw, } static int -mt7603_config(struct ieee80211_hw *hw, u32 changed) +mt7603_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct mt7603_dev *dev = hw->priv; int ret = 0; @@ -657,7 +657,8 @@ mt7603_sta_rate_tbl_update(struct ieee80211_hw *hw, struct ieee80211_vif *vif, } static void -mt7603_set_coverage_class(struct ieee80211_hw *hw, s16 coverage_class) +mt7603_set_coverage_class(struct ieee80211_hw *hw, int radio_idx, + s16 coverage_class) { struct mt7603_dev *dev = hw->priv; diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/main.c b/drivers/net/wireless/mediatek/mt76/mt7615/main.c index 8a37fb37f77d..15fe155ac3f3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/main.c @@ -420,7 +420,7 @@ static int mt7615_set_sar_specs(struct ieee80211_hw *hw, return mt76_update_channel(phy->mt76); } -static int mt7615_config(struct ieee80211_hw *hw, u32 changed) +static int mt7615_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct mt7615_dev *dev = mt7615_hw_dev(hw); struct mt7615_phy *phy = mt7615_hw_phy(hw); @@ -784,7 +784,8 @@ static void mt7615_tx(struct ieee80211_hw *hw, mt76_connac_pm_queue_skb(hw, &dev->pm, wcid, skb); } -static int mt7615_set_rts_threshold(struct ieee80211_hw *hw, u32 val) +static int mt7615_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 val) { struct mt7615_dev *dev = mt7615_hw_dev(hw); struct mt7615_phy *phy = mt7615_hw_phy(hw); @@ -972,7 +973,8 @@ mt7615_offset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif, } static void -mt7615_set_coverage_class(struct ieee80211_hw *hw, s16 coverage_class) +mt7615_set_coverage_class(struct ieee80211_hw *hw, int radio_idx, + s16 coverage_class) { struct mt7615_phy *phy = mt7615_hw_phy(hw); struct mt7615_dev *dev = phy->dev; @@ -984,7 +986,8 @@ mt7615_set_coverage_class(struct ieee80211_hw *hw, s16 coverage_class) } static int -mt7615_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +mt7615_set_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct mt7615_dev *dev = mt7615_hw_dev(hw); struct mt7615_phy *phy = mt7615_hw_phy(hw); diff --git a/drivers/net/wireless/mediatek/mt76/mt76x0/main.c b/drivers/net/wireless/mediatek/mt76/mt76x0/main.c index 4aa2dcedc874..a5c40d350612 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x0/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x0/main.c @@ -57,7 +57,7 @@ out: } EXPORT_SYMBOL_GPL(mt76x0_set_sar_specs); -int mt76x0_config(struct ieee80211_hw *hw, u32 changed) +int mt76x0_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct mt76x02_dev *dev = hw->priv; diff --git a/drivers/net/wireless/mediatek/mt76/mt76x0/mt76x0.h b/drivers/net/wireless/mediatek/mt76/mt76x0/mt76x0.h index 50f755344968..e5bc14d4c712 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x0/mt76x0.h +++ b/drivers/net/wireless/mediatek/mt76/mt76x0/mt76x0.h @@ -48,7 +48,7 @@ void mt76x0_chip_onoff(struct mt76x02_dev *dev, bool enable, bool reset); void mt76x0_mac_stop(struct mt76x02_dev *dev); -int mt76x0_config(struct ieee80211_hw *hw, u32 changed); +int mt76x0_config(struct ieee80211_hw *hw, int radio_idx, u32 changed); int mt76x0_set_channel(struct mt76_phy *mphy); int mt76x0_set_sar_specs(struct ieee80211_hw *hw, const struct cfg80211_sar_specs *sar); diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02.h b/drivers/net/wireless/mediatek/mt76/mt76x02.h index 4cd63bacd742..2094c7d2af81 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x02.h +++ b/drivers/net/wireless/mediatek/mt76/mt76x02.h @@ -183,8 +183,8 @@ void mt76x02_wdt_work(struct work_struct *work); void mt76x02_tx_set_txpwr_auto(struct mt76x02_dev *dev, s8 txpwr); void mt76x02_set_tx_ackto(struct mt76x02_dev *dev); void mt76x02_set_coverage_class(struct ieee80211_hw *hw, - s16 coverage_class); -int mt76x02_set_rts_threshold(struct ieee80211_hw *hw, u32 val); + int radio_idx, s16 coverage_class); +int mt76x02_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, u32 val); void mt76x02_remove_hdr_pad(struct sk_buff *skb, int len); bool mt76x02_tx_status_data(struct mt76_dev *mdev, u8 *update); void mt76x02_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q, diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_util.c b/drivers/net/wireless/mediatek/mt76/mt76x02_util.c index 4fb30589fa7a..7dfcb20c692c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x02_util.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x02_util.c @@ -548,7 +548,7 @@ void mt76x02_set_tx_ackto(struct mt76x02_dev *dev) EXPORT_SYMBOL_GPL(mt76x02_set_tx_ackto); void mt76x02_set_coverage_class(struct ieee80211_hw *hw, - s16 coverage_class) + int radio_idx, s16 coverage_class) { struct mt76x02_dev *dev = hw->priv; @@ -559,7 +559,7 @@ void mt76x02_set_coverage_class(struct ieee80211_hw *hw, } EXPORT_SYMBOL_GPL(mt76x02_set_coverage_class); -int mt76x02_set_rts_threshold(struct ieee80211_hw *hw, u32 val) +int mt76x02_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, u32 val) { struct mt76x02_dev *dev = hw->priv; diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2/pci_main.c b/drivers/net/wireless/mediatek/mt76/mt76x2/pci_main.c index eb70130d2711..c5dfb06d81e8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x2/pci_main.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x2/pci_main.c @@ -54,7 +54,7 @@ int mt76x2e_set_channel(struct mt76_phy *phy) } static int -mt76x2_config(struct ieee80211_hw *hw, u32 changed) +mt76x2_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct mt76x02_dev *dev = hw->priv; @@ -99,8 +99,8 @@ mt76x2_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, { } -static int mt76x2_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, - u32 rx_ant) +static int mt76x2_set_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct mt76x02_dev *dev = hw->priv; diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2/usb_main.c b/drivers/net/wireless/mediatek/mt76/mt76x2/usb_main.c index 83e7061b10e2..6671c53faf9f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x2/usb_main.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x2/usb_main.c @@ -50,7 +50,7 @@ int mt76x2u_set_channel(struct mt76_phy *mphy) } static int -mt76x2u_config(struct ieee80211_hw *hw, u32 changed) +mt76x2u_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct mt76x02_dev *dev = hw->priv; int err = 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index 3aa31c5cefa6..fe0639c14bf9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -449,7 +449,8 @@ out: return err; } -static int mt7915_config(struct ieee80211_hw *hw, u32 changed) +static int mt7915_config(struct ieee80211_hw *hw, int radio_idx, + u32 changed) { struct mt7915_dev *dev = mt7915_hw_dev(hw); struct mt7915_phy *phy = mt7915_hw_phy(hw); @@ -906,7 +907,8 @@ static void mt7915_tx(struct ieee80211_hw *hw, mt76_tx(mphy, control->sta, wcid, skb); } -static int mt7915_set_rts_threshold(struct ieee80211_hw *hw, u32 val) +static int mt7915_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 val) { struct mt7915_dev *dev = mt7915_hw_dev(hw); struct mt7915_phy *phy = mt7915_hw_phy(hw); @@ -1102,7 +1104,8 @@ mt7915_offset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif, } static void -mt7915_set_coverage_class(struct ieee80211_hw *hw, s16 coverage_class) +mt7915_set_coverage_class(struct ieee80211_hw *hw, int radio_idx, + s16 coverage_class) { struct mt7915_phy *phy = mt7915_hw_phy(hw); struct mt7915_dev *dev = phy->dev; @@ -1114,7 +1117,7 @@ mt7915_set_coverage_class(struct ieee80211_hw *hw, s16 coverage_class) } static int -mt7915_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +mt7915_set_antenna(struct ieee80211_hw *hw, int radio_idx, u32 tx_ant, u32 rx_ant) { struct mt7915_dev *dev = mt7915_hw_dev(hw); struct mt7915_phy *phy = mt7915_hw_phy(hw); @@ -1655,7 +1658,7 @@ mt7915_twt_teardown_request(struct ieee80211_hw *hw, } static int -mt7915_set_frag_threshold(struct ieee80211_hw *hw, u32 val) +mt7915_set_frag_threshold(struct ieee80211_hw *hw, int radio_idx, u32 val) { return 0; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index 1fffa43379b2..1678204296d7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -624,7 +624,7 @@ void mt7921_set_runtime_pm(struct mt792x_dev *dev) mt76_connac_mcu_set_deep_sleep(&dev->mt76, pm->ds_enable); } -static int mt7921_config(struct ieee80211_hw *hw, u32 changed) +static int mt7921_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct mt792x_dev *dev = mt792x_hw_dev(hw); struct mt792x_phy *phy = mt792x_hw_phy(hw); @@ -907,7 +907,8 @@ void mt7921_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif, } EXPORT_SYMBOL_GPL(mt7921_mac_sta_remove); -static int mt7921_set_rts_threshold(struct ieee80211_hw *hw, u32 val) +static int mt7921_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 val) { struct mt792x_dev *dev = mt792x_hw_dev(hw); @@ -1088,7 +1089,8 @@ mt7921_stop_sched_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif) } static int -mt7921_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +mt7921_set_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct mt792x_dev *dev = mt792x_hw_dev(hw); struct mt792x_phy *phy = mt792x_hw_phy(hw); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 94b0099dcd41..ed7cd75aa6bc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -757,7 +757,7 @@ void mt7925_set_runtime_pm(struct mt792x_dev *dev) mt7925_mcu_set_deep_sleep(dev, pm->ds_enable); } -static int mt7925_config(struct ieee80211_hw *hw, u32 changed) +static int mt7925_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct mt792x_dev *dev = mt792x_hw_dev(hw); int ret = 0; @@ -1265,7 +1265,8 @@ void mt7925_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif, } EXPORT_SYMBOL_GPL(mt7925_mac_sta_remove); -static int mt7925_set_rts_threshold(struct ieee80211_hw *hw, u32 val) +static int mt7925_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 val) { struct mt792x_dev *dev = mt792x_hw_dev(hw); @@ -1507,7 +1508,8 @@ mt7925_stop_sched_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif) } static int -mt7925_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +mt7925_set_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct mt792x_dev *dev = mt792x_hw_dev(hw); struct mt792x_phy *phy = mt792x_hw_phy(hw); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index e0359d431eca..443d397d9961 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -412,7 +412,8 @@ void mt792x_sta_statistics(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct station_info *sinfo); -void mt792x_set_coverage_class(struct ieee80211_hw *hw, s16 coverage_class); +void mt792x_set_coverage_class(struct ieee80211_hw *hw, int radio_idx, + s16 coverage_class); void mt792x_dma_cleanup(struct mt792x_dev *dev); int mt792x_dma_enable(struct mt792x_dev *dev); int mt792x_wpdma_reset(struct mt792x_dev *dev, bool force); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index a50c1723ca29..43a7ac0f718e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -579,7 +579,8 @@ void mt792x_sta_statistics(struct ieee80211_hw *hw, } EXPORT_SYMBOL_GPL(mt792x_sta_statistics); -void mt792x_set_coverage_class(struct ieee80211_hw *hw, s16 coverage_class) +void mt792x_set_coverage_class(struct ieee80211_hw *hw, int radio_idx, + s16 coverage_class) { struct mt792x_phy *phy = mt792x_hw_phy(hw); struct mt792x_dev *dev = phy->dev; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 78ae9f5cb176..5283aee619a9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -591,7 +591,7 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, return err; } -static int mt7996_config(struct ieee80211_hw *hw, u32 changed) +static int mt7996_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { return 0; } @@ -1251,7 +1251,8 @@ unlock: rcu_read_unlock(); } -static int mt7996_set_rts_threshold(struct ieee80211_hw *hw, u32 val) +static int mt7996_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 val) { struct mt7996_dev *dev = mt7996_hw_dev(hw); int i, ret = 0; @@ -1491,7 +1492,8 @@ unlock: } static void -mt7996_set_coverage_class(struct ieee80211_hw *hw, s16 coverage_class) +mt7996_set_coverage_class(struct ieee80211_hw *hw, int radio_idx, + s16 coverage_class) { struct mt7996_dev *dev = mt7996_hw_dev(hw); struct mt7996_phy *phy; @@ -1505,7 +1507,8 @@ mt7996_set_coverage_class(struct ieee80211_hw *hw, s16 coverage_class) } static int -mt7996_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +mt7996_set_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct mt7996_dev *dev = mt7996_hw_dev(hw); int i; diff --git a/drivers/net/wireless/mediatek/mt7601u/main.c b/drivers/net/wireless/mediatek/mt7601u/main.c index 7570c6ceecea..05ba43e1985c 100644 --- a/drivers/net/wireless/mediatek/mt7601u/main.c +++ b/drivers/net/wireless/mediatek/mt7601u/main.c @@ -78,7 +78,7 @@ static void mt7601u_remove_interface(struct ieee80211_hw *hw, dev->wcid_mask[wcid / BITS_PER_LONG] &= ~BIT(wcid % BITS_PER_LONG); } -static int mt7601u_config(struct ieee80211_hw *hw, u32 changed) +static int mt7601u_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct mt7601u_dev *dev = hw->priv; int ret = 0; @@ -334,7 +334,8 @@ mt7601u_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, return mt76_mac_wcid_set_key(dev, msta->wcid.idx, key); } -static int mt7601u_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int mt7601u_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { struct mt7601u_dev *dev = hw->priv; diff --git a/drivers/net/wireless/microchip/wilc1000/cfg80211.c b/drivers/net/wireless/microchip/wilc1000/cfg80211.c index e7aa0f991923..a395829ebadf 100644 --- a/drivers/net/wireless/microchip/wilc1000/cfg80211.c +++ b/drivers/net/wireless/microchip/wilc1000/cfg80211.c @@ -800,7 +800,7 @@ static int change_bss(struct wiphy *wiphy, struct net_device *dev, return 0; } -static int set_wiphy_params(struct wiphy *wiphy, u32 changed) +static int set_wiphy_params(struct wiphy *wiphy, int radio_idx, u32 changed) { int ret = -EINVAL; struct cfg_param_attr cfg_param_val; @@ -1637,7 +1637,8 @@ static void wilc_set_wakeup(struct wiphy *wiphy, bool enabled) } static int set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, - enum nl80211_tx_power_setting type, int mbm) + int radio_idx, enum nl80211_tx_power_setting type, + int mbm) { int ret; int srcu_idx; @@ -1669,7 +1670,7 @@ static int set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, } static int get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, - unsigned int link_id, int *dbm) + int radio_idx, unsigned int link_id, int *dbm) { int ret; struct wilc_vif *vif = netdev_priv(wdev->netdev); diff --git a/drivers/net/wireless/purelifi/plfxlc/mac.c b/drivers/net/wireless/purelifi/plfxlc/mac.c index 82d1bf7edba2..d375ad60167f 100644 --- a/drivers/net/wireless/purelifi/plfxlc/mac.c +++ b/drivers/net/wireless/purelifi/plfxlc/mac.c @@ -531,7 +531,7 @@ static void plfxlc_op_remove_interface(struct ieee80211_hw *hw, mac->vif = NULL; } -static int plfxlc_op_config(struct ieee80211_hw *hw, u32 changed) +static int plfxlc_op_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { return 0; } @@ -677,7 +677,8 @@ static void plfxlc_get_et_stats(struct ieee80211_hw *hw, data[1] = mac->crc_errors; } -static int plfxlc_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int plfxlc_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { return 0; } diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c index 0b2282528342..f1188368e66b 100644 --- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c +++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c @@ -370,7 +370,8 @@ static int qtnf_stop_ap(struct wiphy *wiphy, struct net_device *dev, return ret; } -static int qtnf_set_wiphy_params(struct wiphy *wiphy, u32 changed) +static int qtnf_set_wiphy_params(struct wiphy *wiphy, int radio_idx, + u32 changed) { struct qtnf_wmac *mac = wiphy_priv(wiphy); struct qtnf_vif *vif; @@ -881,7 +882,7 @@ static int qtnf_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, } static int qtnf_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, - unsigned int link_id, int *dbm) + int radio_idx, unsigned int link_id, int *dbm) { struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev); int ret; @@ -894,7 +895,8 @@ static int qtnf_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, } static int qtnf_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, - enum nl80211_tx_power_setting type, int mbm) + int radio_idx, enum nl80211_tx_power_setting type, + int mbm) { struct qtnf_vif *vif; int ret; diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c index b7ea606bda08..4b5a7c9b6499 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c @@ -12100,7 +12100,7 @@ void rt2800_get_key_seq(struct ieee80211_hw *hw, } EXPORT_SYMBOL_GPL(rt2800_get_key_seq); -int rt2800_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +int rt2800_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, u32 value) { struct rt2x00_dev *rt2x00dev = hw->priv; u32 reg; diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800lib.h b/drivers/net/wireless/ralink/rt2x00/rt2800lib.h index 194de676df8f..620a3d9872ce 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2800lib.h +++ b/drivers/net/wireless/ralink/rt2x00/rt2800lib.h @@ -253,7 +253,8 @@ int rt2800_probe_hw(struct rt2x00_dev *rt2x00dev); void rt2800_get_key_seq(struct ieee80211_hw *hw, struct ieee80211_key_conf *key, struct ieee80211_key_seq *seq); -int rt2800_set_rts_threshold(struct ieee80211_hw *hw, u32 value); +int rt2800_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value); int rt2800_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, unsigned int link_id, u16 queue_idx, diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00.h b/drivers/net/wireless/ralink/rt2x00/rt2x00.h index dfb4bb370f01..09b9d1f9f793 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2x00.h +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00.h @@ -1457,7 +1457,7 @@ int rt2x00mac_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif); void rt2x00mac_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif); -int rt2x00mac_config(struct ieee80211_hw *hw, u32 changed); +int rt2x00mac_config(struct ieee80211_hw *hw, int radio_idx, u32 changed); void rt2x00mac_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags, unsigned int *total_flags, @@ -1489,8 +1489,10 @@ int rt2x00mac_conf_tx(struct ieee80211_hw *hw, void rt2x00mac_rfkill_poll(struct ieee80211_hw *hw); void rt2x00mac_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u32 queues, bool drop); -int rt2x00mac_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant); -int rt2x00mac_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant); +int rt2x00mac_set_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant); +int rt2x00mac_get_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 *tx_ant, u32 *rx_ant); void rt2x00mac_get_ringparam(struct ieee80211_hw *hw, u32 *tx, u32 *tx_max, u32 *rx, u32 *rx_max); bool rt2x00mac_tx_frames_pending(struct ieee80211_hw *hw); diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00mac.c b/drivers/net/wireless/ralink/rt2x00/rt2x00mac.c index 451632488805..3bc0c1c906c9 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2x00mac.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00mac.c @@ -304,7 +304,7 @@ void rt2x00mac_remove_interface(struct ieee80211_hw *hw, } EXPORT_SYMBOL_GPL(rt2x00mac_remove_interface); -int rt2x00mac_config(struct ieee80211_hw *hw, u32 changed) +int rt2x00mac_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct rt2x00_dev *rt2x00dev = hw->priv; struct ieee80211_conf *conf = &hw->conf; @@ -740,7 +740,8 @@ void rt2x00mac_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, } EXPORT_SYMBOL_GPL(rt2x00mac_flush); -int rt2x00mac_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +int rt2x00mac_set_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct rt2x00_dev *rt2x00dev = hw->priv; struct link_ant *ant = &rt2x00dev->link.ant; @@ -785,7 +786,8 @@ int rt2x00mac_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) } EXPORT_SYMBOL_GPL(rt2x00mac_set_antenna); -int rt2x00mac_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +int rt2x00mac_get_antenna(struct ieee80211_hw *hw, int radio_idx, + u32 *tx_ant, u32 *rx_ant) { struct rt2x00_dev *rt2x00dev = hw->priv; struct link_ant *ant = &rt2x00dev->link.ant; diff --git a/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c b/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c index ded8d4d59289..2905baea6239 100644 --- a/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c +++ b/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c @@ -1370,7 +1370,7 @@ static void rtl8180_remove_interface(struct ieee80211_hw *dev, priv->vif = NULL; } -static int rtl8180_config(struct ieee80211_hw *dev, u32 changed) +static int rtl8180_config(struct ieee80211_hw *dev, int radio_idx, u32 changed) { struct rtl8180_priv *priv = dev->priv; struct ieee80211_conf *conf = &dev->conf; diff --git a/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c b/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c index 220ac5bdf279..8857bb542c7f 100644 --- a/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c +++ b/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c @@ -1151,7 +1151,7 @@ static void rtl8187_remove_interface(struct ieee80211_hw *dev, mutex_unlock(&priv->conf_mutex); } -static int rtl8187_config(struct ieee80211_hw *dev, u32 changed) +static int rtl8187_config(struct ieee80211_hw *dev, int radio_idx, u32 changed) { struct rtl8187_priv *priv = dev->priv; struct ieee80211_conf *conf = &dev->conf; diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c index 569856ca677f..496836f716aa 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c @@ -4552,7 +4552,8 @@ static void rtl8xxxu_cam_write(struct rtl8xxxu_priv *priv, } static -int rtl8xxxu_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +int rtl8xxxu_get_antenna(struct ieee80211_hw *hw, int radio_idx, u32 *tx_ant, + u32 *rx_ant) { struct rtl8xxxu_priv *priv = hw->priv; @@ -6839,7 +6840,7 @@ static void rtl8xxxu_remove_interface(struct ieee80211_hw *hw, priv->vifs[rtlvif->port_num] = NULL; } -static int rtl8xxxu_config(struct ieee80211_hw *hw, u32 changed) +static int rtl8xxxu_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct rtl8xxxu_priv *priv = hw->priv; struct device *dev = &priv->udev->dev; @@ -6988,7 +6989,8 @@ static void rtl8xxxu_configure_filter(struct ieee80211_hw *hw, FIF_PROBE_REQ); } -static int rtl8xxxu_set_rts_threshold(struct ieee80211_hw *hw, u32 rts) +static int rtl8xxxu_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 rts) { if (rts > 2347 && rts != (u32)-1) return -EINVAL; diff --git a/drivers/net/wireless/realtek/rtlwifi/core.c b/drivers/net/wireless/realtek/rtlwifi/core.c index 819cf519e66e..22633c301564 100644 --- a/drivers/net/wireless/realtek/rtlwifi/core.c +++ b/drivers/net/wireless/realtek/rtlwifi/core.c @@ -566,7 +566,7 @@ static int rtl_op_resume(struct ieee80211_hw *hw) } #endif -static int rtl_op_config(struct ieee80211_hw *hw, u32 changed) +static int rtl_op_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct rtl_priv *rtlpriv = rtl_priv(hw); struct rtl_phy *rtlphy = &(rtlpriv->phy); diff --git a/drivers/net/wireless/realtek/rtw88/mac80211.c b/drivers/net/wireless/realtek/rtw88/mac80211.c index 77f9fbe1870c..766f22d31079 100644 --- a/drivers/net/wireless/realtek/rtw88/mac80211.c +++ b/drivers/net/wireless/realtek/rtw88/mac80211.c @@ -71,7 +71,7 @@ static void rtw_ops_stop(struct ieee80211_hw *hw, bool suspend) mutex_unlock(&rtwdev->mutex); } -static int rtw_ops_config(struct ieee80211_hw *hw, u32 changed) +static int rtw_ops_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct rtw_dev *rtwdev = hw->priv; int ret = 0; @@ -708,7 +708,8 @@ static void rtw_ops_mgd_prepare_tx(struct ieee80211_hw *hw, mutex_unlock(&rtwdev->mutex); } -static int rtw_ops_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int rtw_ops_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { struct rtw_dev *rtwdev = hw->priv; @@ -797,6 +798,7 @@ static int rtw_ops_set_bitrate_mask(struct ieee80211_hw *hw, } static int rtw_ops_set_antenna(struct ieee80211_hw *hw, + int radio_idx, u32 tx_antenna, u32 rx_antenna) { @@ -808,13 +810,14 @@ static int rtw_ops_set_antenna(struct ieee80211_hw *hw, return -EOPNOTSUPP; mutex_lock(&rtwdev->mutex); - ret = chip->ops->set_antenna(rtwdev, tx_antenna, rx_antenna); + ret = chip->ops->set_antenna(rtwdev, -1, tx_antenna, rx_antenna); mutex_unlock(&rtwdev->mutex); return ret; } static int rtw_ops_get_antenna(struct ieee80211_hw *hw, + int radio_idx, u32 *tx_antenna, u32 *rx_antenna) { diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h index b0f1fabe9554..7ae67143e909 100644 --- a/drivers/net/wireless/realtek/rtw88/main.h +++ b/drivers/net/wireless/realtek/rtw88/main.h @@ -873,7 +873,7 @@ struct rtw_chip_ops { void (*set_tx_power_index)(struct rtw_dev *rtwdev); int (*rsvd_page_dump)(struct rtw_dev *rtwdev, u8 *buf, u32 offset, u32 size); - int (*set_antenna)(struct rtw_dev *rtwdev, + int (*set_antenna)(struct rtw_dev *rtwdev, int radio_idx, u32 antenna_tx, u32 antenna_rx); void (*cfg_ldo25)(struct rtw_dev *rtwdev, bool enable); diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822b.c b/drivers/net/wireless/realtek/rtw88/rtw8822b.c index ab199eaea3c7..710126379e77 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c @@ -983,6 +983,7 @@ static bool rtw8822b_check_rf_path(u8 antenna) } static int rtw8822b_set_antenna(struct rtw_dev *rtwdev, + int radio_idx, u32 antenna_tx, u32 antenna_rx) { diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c index 017d959de3ce..0ce6aa10493e 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c @@ -2767,6 +2767,7 @@ static void rtw8822c_set_tx_power_index(struct rtw_dev *rtwdev) } static int rtw8822c_set_antenna(struct rtw_dev *rtwdev, + int radio_idx, u32 antenna_tx, u32 antenna_rx) { diff --git a/drivers/net/wireless/realtek/rtw89/mac80211.c b/drivers/net/wireless/realtek/rtw89/mac80211.c index a47971003bd4..b9e046208424 100644 --- a/drivers/net/wireless/realtek/rtw89/mac80211.c +++ b/drivers/net/wireless/realtek/rtw89/mac80211.c @@ -72,7 +72,7 @@ static void rtw89_ops_stop(struct ieee80211_hw *hw, bool suspend) rtw89_core_stop(rtwdev); } -static int rtw89_ops_config(struct ieee80211_hw *hw, u32 changed) +static int rtw89_ops_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct rtw89_dev *rtwdev = hw->priv; @@ -1007,7 +1007,8 @@ static int rtw89_ops_ampdu_action(struct ieee80211_hw *hw, return 0; } -static int rtw89_ops_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int rtw89_ops_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { struct rtw89_dev *rtwdev = hw->priv; @@ -1119,7 +1120,7 @@ static int rtw89_ops_set_bitrate_mask(struct ieee80211_hw *hw, } static -int rtw89_ops_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +int rtw89_ops_set_antenna(struct ieee80211_hw *hw, int radio_idx, u32 tx_ant, u32 rx_ant) { struct rtw89_dev *rtwdev = hw->priv; struct rtw89_hal *hal = &rtwdev->hal; @@ -1142,7 +1143,8 @@ int rtw89_ops_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) } static -int rtw89_ops_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +int rtw89_ops_get_antenna(struct ieee80211_hw *hw, int radio_idx, u32 *tx_ant, + u32 *rx_ant) { struct rtw89_dev *rtwdev = hw->priv; struct rtw89_hal *hal = &rtwdev->hal; diff --git a/drivers/net/wireless/rsi/rsi_91x_mac80211.c b/drivers/net/wireless/rsi/rsi_91x_mac80211.c index 0e115b428f96..f3a853edfc11 100644 --- a/drivers/net/wireless/rsi/rsi_91x_mac80211.c +++ b/drivers/net/wireless/rsi/rsi_91x_mac80211.c @@ -656,11 +656,13 @@ static int rsi_config_power(struct ieee80211_hw *hw) * requests. The stack calls this function to * change hardware configuration, e.g., channel. * @hw: Pointer to the ieee80211_hw structure. + * @radio_idx: Radio index. * @changed: Changed flags set. * * Return: 0 on success, negative error code on failure. */ static int rsi_mac80211_config(struct ieee80211_hw *hw, + int radio_idx, u32 changed) { struct rsi_hw *adapter = hw->priv; @@ -1201,12 +1203,13 @@ unlock: /** * rsi_mac80211_set_rts_threshold() - This function sets rts threshold value. * @hw: Pointer to the ieee80211_hw structure. + * @radio_idx: Radio index. * @value: Rts threshold value. * * Return: 0 on success. */ static int rsi_mac80211_set_rts_threshold(struct ieee80211_hw *hw, - u32 value) + int radio_idx, u32 value) { struct rsi_hw *adapter = hw->priv; struct rsi_common *common = adapter->priv; @@ -1583,12 +1586,14 @@ static int rsi_mac80211_sta_remove(struct ieee80211_hw *hw, * rsi_mac80211_set_antenna() - This function is used to configure * tx and rx antennas. * @hw: Pointer to the ieee80211_hw structure. + * @radio_idx: Radio index * @tx_ant: Bitmap for tx antenna * @rx_ant: Bitmap for rx antenna * * Return: 0 on success, Negative error code on failure. */ static int rsi_mac80211_set_antenna(struct ieee80211_hw *hw, + int radio_idx, u32 tx_ant, u32 rx_ant) { struct rsi_hw *adapter = hw->priv; @@ -1634,12 +1639,14 @@ fail_set_antenna: * tx and rx antennas. * * @hw: Pointer to the ieee80211_hw structure. + * @radio_idx: Radio index * @tx_ant: Bitmap for tx antenna * @rx_ant: Bitmap for rx antenna * * Return: 0 on success, negative error codes on failure. */ static int rsi_mac80211_get_antenna(struct ieee80211_hw *hw, + int radio_idx, u32 *tx_ant, u32 *rx_ant) { struct rsi_hw *adapter = hw->priv; diff --git a/drivers/net/wireless/silabs/wfx/sta.c b/drivers/net/wireless/silabs/wfx/sta.c index e95b9ded17d9..d12fcc755701 100644 --- a/drivers/net/wireless/silabs/wfx/sta.c +++ b/drivers/net/wireless/silabs/wfx/sta.c @@ -220,7 +220,7 @@ int wfx_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, return 0; } -int wfx_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +int wfx_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, u32 value) { struct wfx_dev *wdev = hw->priv; struct wfx_vif *wvif = NULL; @@ -706,7 +706,7 @@ void wfx_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif wvif->channel = NULL; } -int wfx_config(struct ieee80211_hw *hw, u32 changed) +int wfx_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { return 0; } diff --git a/drivers/net/wireless/silabs/wfx/sta.h b/drivers/net/wireless/silabs/wfx/sta.h index 8702eed5267f..b4812b294f3c 100644 --- a/drivers/net/wireless/silabs/wfx/sta.h +++ b/drivers/net/wireless/silabs/wfx/sta.h @@ -21,8 +21,8 @@ struct wfx_sta_priv { /* mac80211 interface */ int wfx_start(struct ieee80211_hw *hw); void wfx_stop(struct ieee80211_hw *hw, bool suspend); -int wfx_config(struct ieee80211_hw *hw, u32 changed); -int wfx_set_rts_threshold(struct ieee80211_hw *hw, u32 value); +int wfx_config(struct ieee80211_hw *hw, int radio_idx, u32 changed); +int wfx_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, u32 value); void wfx_set_default_unicast_key(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int idx); void wfx_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags, unsigned int *total_flags, u64 unused); diff --git a/drivers/net/wireless/st/cw1200/sta.c b/drivers/net/wireless/st/cw1200/sta.c index 5dd7f6a38900..b1dd76e8aecb 100644 --- a/drivers/net/wireless/st/cw1200/sta.c +++ b/drivers/net/wireless/st/cw1200/sta.c @@ -321,7 +321,7 @@ int cw1200_change_interface(struct ieee80211_hw *dev, return ret; } -int cw1200_config(struct ieee80211_hw *dev, u32 changed) +int cw1200_config(struct ieee80211_hw *dev, int radio_idx, u32 changed) { int ret = 0; struct cw1200_common *priv = dev->priv; @@ -857,7 +857,8 @@ void cw1200_wep_key_work(struct work_struct *work) wsm_unlock_tx(priv); } -int cw1200_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +int cw1200_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { int ret = 0; __le32 val32; diff --git a/drivers/net/wireless/st/cw1200/sta.h b/drivers/net/wireless/st/cw1200/sta.h index b955b92cfd73..b4f04371668d 100644 --- a/drivers/net/wireless/st/cw1200/sta.h +++ b/drivers/net/wireless/st/cw1200/sta.h @@ -22,7 +22,7 @@ int cw1200_change_interface(struct ieee80211_hw *dev, struct ieee80211_vif *vif, enum nl80211_iftype new_type, bool p2p); -int cw1200_config(struct ieee80211_hw *dev, u32 changed); +int cw1200_config(struct ieee80211_hw *dev, int radio_idx, u32 changed); void cw1200_configure_filter(struct ieee80211_hw *dev, unsigned int changed_flags, unsigned int *total_flags, @@ -36,7 +36,8 @@ int cw1200_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *key); -int cw1200_set_rts_threshold(struct ieee80211_hw *hw, u32 value); +int cw1200_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value); void cw1200_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u32 queues, bool drop); diff --git a/drivers/net/wireless/ti/wl1251/main.c b/drivers/net/wireless/ti/wl1251/main.c index bb53d681c11b..69fc51f183ad 100644 --- a/drivers/net/wireless/ti/wl1251/main.c +++ b/drivers/net/wireless/ti/wl1251/main.c @@ -589,7 +589,7 @@ static bool wl1251_can_do_pm(struct ieee80211_conf *conf, struct wl1251 *wl) return (conf->flags & IEEE80211_CONF_PS) && !wl->monitor_present; } -static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed) +static int wl1251_op_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct wl1251 *wl = hw->priv; struct ieee80211_conf *conf = &hw->conf; @@ -1051,7 +1051,8 @@ out: return ret; } -static int wl1251_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int wl1251_op_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { struct wl1251 *wl = hw->priv; int ret; diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c index f93c95edd991..6116a8522d96 100644 --- a/drivers/net/wireless/ti/wlcore/main.c +++ b/drivers/net/wireless/ti/wlcore/main.c @@ -3166,7 +3166,7 @@ static int wl12xx_config_vif(struct wl1271 *wl, struct wl12xx_vif *wlvif, return 0; } -static int wl1271_op_config(struct ieee80211_hw *hw, u32 changed) +static int wl1271_op_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct wl1271 *wl = hw->priv; struct wl12xx_vif *wlvif; @@ -3895,7 +3895,8 @@ out: return 0; } -static int wl1271_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value) +static int wl1271_op_set_frag_threshold(struct ieee80211_hw *hw, + int radio_idx, u32 value) { struct wl1271 *wl = hw->priv; int ret = 0; @@ -3924,7 +3925,8 @@ out: return ret; } -static int wl1271_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int wl1271_op_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) { struct wl1271 *wl = hw->priv; struct wl12xx_vif *wlvif; diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.c b/drivers/net/wireless/virtual/mac80211_hwsim.c index f6add19d1da1..eefe8da3b14d 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim.c @@ -2381,7 +2381,8 @@ static const char * const hwsim_chanwidths[] = { [NL80211_CHAN_WIDTH_320] = "eht320", }; -static int mac80211_hwsim_config(struct ieee80211_hw *hw, u32 changed) +static int mac80211_hwsim_config(struct ieee80211_hw *hw, int radio_idx, + u32 changed) { struct mac80211_hwsim_data *data = hw->priv; struct ieee80211_conf *conf = &hw->conf; @@ -3338,7 +3339,8 @@ static int mac80211_hwsim_tx_last_beacon(struct ieee80211_hw *hw) return 1; } -static int mac80211_hwsim_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +static int mac80211_hwsim_set_rts_threshold(struct ieee80211_hw *hw, + int radio_idx, u32 value) { return -EOPNOTSUPP; } diff --git a/drivers/net/wireless/zydas/zd1211rw/zd_mac.c b/drivers/net/wireless/zydas/zd1211rw/zd_mac.c index 9653dbaac3c0..f7c56174424d 100644 --- a/drivers/net/wireless/zydas/zd1211rw/zd_mac.c +++ b/drivers/net/wireless/zydas/zd1211rw/zd_mac.c @@ -1133,7 +1133,7 @@ static void zd_op_remove_interface(struct ieee80211_hw *hw, zd_mac_free_cur_beacon(mac); } -static int zd_op_config(struct ieee80211_hw *hw, u32 changed) +static int zd_op_config(struct ieee80211_hw *hw, int radio_idx, u32 changed) { struct zd_mac *mac = zd_hw_mac(hw); struct ieee80211_conf *conf = &hw->conf; diff --git a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c index 7fcc46a0bb48..4e29652f8ee7 100644 --- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c +++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c @@ -1298,7 +1298,8 @@ exit: return ret; } -static int cfg80211_rtw_set_wiphy_params(struct wiphy *wiphy, u32 changed) +static int cfg80211_rtw_set_wiphy_params(struct wiphy *wiphy, int radio_idx, + u32 changed) { return 0; } @@ -1795,7 +1796,7 @@ static int cfg80211_rtw_disconnect(struct wiphy *wiphy, struct net_device *ndev, } static int cfg80211_rtw_set_txpower(struct wiphy *wiphy, - struct wireless_dev *wdev, + struct wireless_dev *wdev, int radio_idx, enum nl80211_tx_power_setting type, int mbm) { return 0; @@ -1803,6 +1804,7 @@ static int cfg80211_rtw_set_txpower(struct wiphy *wiphy, static int cfg80211_rtw_get_txpower(struct wiphy *wiphy, struct wireless_dev *wdev, + int radio_idx, unsigned int link_id, int *dbm) { *dbm = (12); diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index eec066f4738a..ffd9564fc840 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -4853,12 +4853,14 @@ struct cfg80211_ops { int (*set_mcast_rate)(struct wiphy *wiphy, struct net_device *dev, int rate[NUM_NL80211_BANDS]); - int (*set_wiphy_params)(struct wiphy *wiphy, u32 changed); + int (*set_wiphy_params)(struct wiphy *wiphy, int radio_idx, + u32 changed); int (*set_tx_power)(struct wiphy *wiphy, struct wireless_dev *wdev, + int radio_idx, enum nl80211_tx_power_setting type, int mbm); int (*get_tx_power)(struct wiphy *wiphy, struct wireless_dev *wdev, - unsigned int link_id, int *dbm); + int radio_idx, unsigned int link_id, int *dbm); void (*rfkill_poll)(struct wiphy *wiphy); @@ -4920,8 +4922,10 @@ struct cfg80211_ops { struct wireless_dev *wdev, struct mgmt_frame_regs *upd); - int (*set_antenna)(struct wiphy *wiphy, u32 tx_ant, u32 rx_ant); - int (*get_antenna)(struct wiphy *wiphy, u32 *tx_ant, u32 *rx_ant); + int (*set_antenna)(struct wiphy *wiphy, int radio_idx, + u32 tx_ant, u32 rx_ant); + int (*get_antenna)(struct wiphy *wiphy, int radio_idx, + u32 *tx_ant, u32 *rx_ant); int (*sched_scan_start)(struct wiphy *wiphy, struct net_device *dev, diff --git a/include/net/mac80211.h b/include/net/mac80211.h index fa2325692abf..a0de0da4d79b 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -4517,7 +4517,7 @@ struct ieee80211_ops { enum nl80211_iftype new_type, bool p2p); void (*remove_interface)(struct ieee80211_hw *hw, struct ieee80211_vif *vif); - int (*config)(struct ieee80211_hw *hw, u32 changed); + int (*config)(struct ieee80211_hw *hw, int radio_idx, u32 changed); void (*bss_info_changed)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *info, @@ -4580,8 +4580,10 @@ struct ieee80211_ops { void (*get_key_seq)(struct ieee80211_hw *hw, struct ieee80211_key_conf *key, struct ieee80211_key_seq *seq); - int (*set_frag_threshold)(struct ieee80211_hw *hw, u32 value); - int (*set_rts_threshold)(struct ieee80211_hw *hw, u32 value); + int (*set_frag_threshold)(struct ieee80211_hw *hw, int radio_idx, + u32 value); + int (*set_rts_threshold)(struct ieee80211_hw *hw, int radio_idx, + u32 value); int (*sta_add)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta); int (*sta_remove)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, @@ -4678,7 +4680,8 @@ struct ieee80211_ops { int (*get_survey)(struct ieee80211_hw *hw, int idx, struct survey_info *survey); void (*rfkill_poll)(struct ieee80211_hw *hw); - void (*set_coverage_class)(struct ieee80211_hw *hw, s16 coverage_class); + void (*set_coverage_class)(struct ieee80211_hw *hw, int radio_idx, + s16 coverage_class); #ifdef CONFIG_NL80211_TESTMODE int (*testmode_cmd)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, void *data, int len); @@ -4693,8 +4696,10 @@ struct ieee80211_ops { void (*channel_switch)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_channel_switch *ch_switch); - int (*set_antenna)(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant); - int (*get_antenna)(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant); + int (*set_antenna)(struct ieee80211_hw *hw, int radio_idx, + u32 tx_ant, u32 rx_ant); + int (*get_antenna)(struct ieee80211_hw *hw, int radio_idx, + u32 *tx_ant, u32 *rx_ant); int (*remain_on_channel)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index a289014abe37..2a71149c3065 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -2907,6 +2907,14 @@ enum nl80211_commands { * APs Support". Drivers may set additional flags that they support * in the kernel or device. * + * @NL80211_ATTR_WIPHY_RADIO_INDEX: (int) Integer attribute denoting the index + * of the radio in interest. Internally a value of -1 is used to + * indicate that the radio id is not given in user-space. This means + * that all the attributes are applicable to all the radios. If there is + * a radio index provided in user-space, the attributes will be + * applicable to that specific radio only. If the radio id is greater + * thank the number of radios, error denoting invalid value is returned. + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3464,6 +3472,8 @@ enum nl80211_attrs { NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS, + NL80211_ATTR_WIPHY_RADIO_INDEX, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 1a17d66dfa75..72cecc304658 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -3045,7 +3045,8 @@ static int ieee80211_set_mcast_rate(struct wiphy *wiphy, struct net_device *dev, return 0; } -static int ieee80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +static int ieee80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, + u32 changed) { struct ieee80211_local *local = wiphy_priv(wiphy); int err; @@ -3053,7 +3054,8 @@ static int ieee80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) if (changed & WIPHY_PARAM_FRAG_THRESHOLD) { ieee80211_check_fast_xmit_all(local); - err = drv_set_frag_threshold(local, wiphy->frag_threshold); + err = drv_set_frag_threshold(local, radio_idx, + wiphy->frag_threshold); if (err) { ieee80211_check_fast_xmit_all(local); @@ -3067,14 +3069,16 @@ static int ieee80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) coverage_class = changed & WIPHY_PARAM_COVERAGE_CLASS ? wiphy->coverage_class : -1; - err = drv_set_coverage_class(local, coverage_class); + err = drv_set_coverage_class(local, radio_idx, + coverage_class); if (err) return err; } if (changed & WIPHY_PARAM_RTS_THRESHOLD) { - err = drv_set_rts_threshold(local, wiphy->rts_threshold); + err = drv_set_rts_threshold(local, radio_idx, + wiphy->rts_threshold); if (err) return err; @@ -3092,18 +3096,19 @@ static int ieee80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) } if (changed & (WIPHY_PARAM_RETRY_SHORT | WIPHY_PARAM_RETRY_LONG)) - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_RETRY_LIMITS); + ieee80211_hw_config(local, radio_idx, + IEEE80211_CONF_CHANGE_RETRY_LIMITS); if (changed & (WIPHY_PARAM_TXQ_LIMIT | WIPHY_PARAM_TXQ_MEMORY_LIMIT | WIPHY_PARAM_TXQ_QUANTUM)) - ieee80211_txq_set_params(local); + ieee80211_txq_set_params(local, radio_idx); return 0; } static int ieee80211_set_tx_power(struct wiphy *wiphy, - struct wireless_dev *wdev, + struct wireless_dev *wdev, int radio_idx, enum nl80211_tx_power_setting type, int mbm) { struct ieee80211_local *local = wiphy_priv(wiphy); @@ -3231,6 +3236,7 @@ static int ieee80211_set_tx_power(struct wiphy *wiphy, static int ieee80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + int radio_idx, unsigned int link_id, int *dbm) { @@ -3409,7 +3415,7 @@ static int ieee80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, } if (ieee80211_hw_check(&local->hw, SUPPORTS_DYNAMIC_PS)) - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); + ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS); ieee80211_recalc_ps(local); ieee80211_recalc_ps_vif(sdata); @@ -4305,7 +4311,8 @@ ieee80211_update_mgmt_frame_registrations(struct wiphy *wiphy, ieee80211_configure_filter(local); } -static int ieee80211_set_antenna(struct wiphy *wiphy, u32 tx_ant, u32 rx_ant) +static int ieee80211_set_antenna(struct wiphy *wiphy, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct ieee80211_local *local = wiphy_priv(wiphy); int ret; @@ -4321,11 +4328,12 @@ static int ieee80211_set_antenna(struct wiphy *wiphy, u32 tx_ant, u32 rx_ant) return 0; } -static int ieee80211_get_antenna(struct wiphy *wiphy, u32 *tx_ant, u32 *rx_ant) +static int ieee80211_get_antenna(struct wiphy *wiphy, int radio_idx, + u32 *tx_ant, u32 *rx_ant) { struct ieee80211_local *local = wiphy_priv(wiphy); - return drv_get_antenna(local, tx_ant, rx_ant); + return drv_get_antenna(local, radio_idx, tx_ant, rx_ant); } static int ieee80211_set_rekey_data(struct wiphy *wiphy, diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index d62f91656a19..4bcbcf9d98b5 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -744,7 +744,7 @@ static int ieee80211_add_chanctx(struct ieee80211_local *local, /* turn idle off *before* setting channel -- some drivers need that */ changed = ieee80211_idle_off(local); if (changed) - ieee80211_hw_config(local, changed); + ieee80211_hw_config(local, -1, changed); err = drv_add_chanctx(local, ctx); if (err) { diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h index ba017bf3fd15..8baebb5636ec 100644 --- a/net/mac80211/driver-ops.h +++ b/net/mac80211/driver-ops.h @@ -143,15 +143,16 @@ int drv_change_interface(struct ieee80211_local *local, void drv_remove_interface(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata); -static inline int drv_config(struct ieee80211_local *local, u32 changed) +static inline int drv_config(struct ieee80211_local *local, int radio_idx, + u32 changed) { int ret; might_sleep(); lockdep_assert_wiphy(local->hw.wiphy); - trace_drv_config(local, changed); - ret = local->ops->config(&local->hw, changed); + trace_drv_config(local, radio_idx, changed); + ret = local->ops->config(&local->hw, radio_idx, changed); trace_drv_return_int(local, ret); return ret; } @@ -387,45 +388,47 @@ static inline void drv_get_key_seq(struct ieee80211_local *local, } static inline int drv_set_frag_threshold(struct ieee80211_local *local, - u32 value) + int radio_idx, u32 value) { int ret = 0; might_sleep(); lockdep_assert_wiphy(local->hw.wiphy); - trace_drv_set_frag_threshold(local, value); + trace_drv_set_frag_threshold(local, radio_idx, value); if (local->ops->set_frag_threshold) - ret = local->ops->set_frag_threshold(&local->hw, value); + ret = local->ops->set_frag_threshold(&local->hw, radio_idx, + value); trace_drv_return_int(local, ret); return ret; } static inline int drv_set_rts_threshold(struct ieee80211_local *local, - u32 value) + int radio_idx, u32 value) { int ret = 0; might_sleep(); lockdep_assert_wiphy(local->hw.wiphy); - trace_drv_set_rts_threshold(local, value); + trace_drv_set_rts_threshold(local, radio_idx, value); if (local->ops->set_rts_threshold) - ret = local->ops->set_rts_threshold(&local->hw, value); + ret = local->ops->set_rts_threshold(&local->hw, radio_idx, + value); trace_drv_return_int(local, ret); return ret; } static inline int drv_set_coverage_class(struct ieee80211_local *local, - s16 value) + int radio_idx, s16 value) { int ret = 0; might_sleep(); lockdep_assert_wiphy(local->hw.wiphy); - trace_drv_set_coverage_class(local, value); + trace_drv_set_coverage_class(local, radio_idx, value); if (local->ops->set_coverage_class) - local->ops->set_coverage_class(&local->hw, value); + local->ops->set_coverage_class(&local->hw, radio_idx, value); else ret = -EOPNOTSUPP; @@ -772,20 +775,21 @@ static inline int drv_set_antenna(struct ieee80211_local *local, might_sleep(); lockdep_assert_wiphy(local->hw.wiphy); if (local->ops->set_antenna) - ret = local->ops->set_antenna(&local->hw, tx_ant, rx_ant); + ret = local->ops->set_antenna(&local->hw, -1, tx_ant, rx_ant); trace_drv_set_antenna(local, tx_ant, rx_ant, ret); return ret; } -static inline int drv_get_antenna(struct ieee80211_local *local, +static inline int drv_get_antenna(struct ieee80211_local *local, int radio_idx, u32 *tx_ant, u32 *rx_ant) { int ret = -EOPNOTSUPP; might_sleep(); lockdep_assert_wiphy(local->hw.wiphy); if (local->ops->get_antenna) - ret = local->ops->get_antenna(&local->hw, tx_ant, rx_ant); - trace_drv_get_antenna(local, *tx_ant, *rx_ant, ret); + ret = local->ops->get_antenna(&local->hw, radio_idx, + tx_ant, rx_ant); + trace_drv_get_antenna(local, radio_idx, *tx_ant, *rx_ant, ret); return ret; } diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 9b9c7209878b..f59a5b38e6f2 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1872,7 +1872,8 @@ u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local, struct ieee80211_rx_status *status, unsigned int mpdu_len, unsigned int mpdu_offset); -int ieee80211_hw_config(struct ieee80211_local *local, u32 changed); +int ieee80211_hw_config(struct ieee80211_local *local, int radio_idx, + u32 changed); int ieee80211_hw_conf_chan(struct ieee80211_local *local); void ieee80211_hw_conf_init(struct ieee80211_local *local); void ieee80211_tx_set_protected(struct ieee80211_tx_data *tx); @@ -2542,7 +2543,7 @@ static inline bool ieee80211_can_run_worker(struct ieee80211_local *local) } int ieee80211_txq_setup_flows(struct ieee80211_local *local); -void ieee80211_txq_set_params(struct ieee80211_local *local); +void ieee80211_txq_set_params(struct ieee80211_local *local, int radio_idx); void ieee80211_txq_teardown_flows(struct ieee80211_local *local); void ieee80211_txq_init(struct ieee80211_sub_if_data *sdata, struct sta_info *sta, diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 7c27f3cd841c..7b2baebb8644 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -146,7 +146,7 @@ void ieee80211_recalc_idle(struct ieee80211_local *local) { u32 change = __ieee80211_recalc_idle(local, false); if (change) - ieee80211_hw_config(local, change); + ieee80211_hw_config(local, -1, change); } static int ieee80211_verify_mac(struct ieee80211_sub_if_data *sdata, u8 *addr, @@ -726,7 +726,7 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do /* do after stop to avoid reconfiguring when we stop anyway */ ieee80211_configure_filter(local); - ieee80211_hw_config(local, hw_reconf_flags); + ieee80211_hw_config(local, -1, hw_reconf_flags); if (local->virt_monitors == local->open_count) ieee80211_add_virtual_monitor(local); @@ -1491,7 +1491,7 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up) if (local->open_count == 1) ieee80211_hw_conf_init(local); else if (hw_reconf_flags) - ieee80211_hw_config(local, hw_reconf_flags); + ieee80211_hw_config(local, -1, hw_reconf_flags); ieee80211_recalc_ps(local); diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 6b6de43d9420..c1c758e76d2e 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -190,7 +190,8 @@ static u32 ieee80211_calc_hw_conf_chan(struct ieee80211_local *local, return changed; } -int ieee80211_hw_config(struct ieee80211_local *local, u32 changed) +int ieee80211_hw_config(struct ieee80211_local *local, int radio_idx, + u32 changed) { int ret = 0; @@ -201,7 +202,7 @@ int ieee80211_hw_config(struct ieee80211_local *local, u32 changed) IEEE80211_CONF_CHANGE_SMPS)); if (changed && local->open_count) { - ret = drv_config(local, changed); + ret = drv_config(local, radio_idx, changed); /* * Goal: * HW reconfiguration should never fail, the driver has told @@ -235,7 +236,7 @@ static int _ieee80211_hw_conf_chan(struct ieee80211_local *local, if (!changed) return 0; - return drv_config(local, changed); + return drv_config(local, -1, changed); } int ieee80211_hw_conf_chan(struct ieee80211_local *local) @@ -269,7 +270,7 @@ void ieee80211_hw_conf_init(struct ieee80211_local *local) ctx ? &ctx->conf : NULL); } - WARN_ON(drv_config(local, changed)); + WARN_ON(drv_config(local, -1, changed)); } int ieee80211_emulate_add_chanctx(struct ieee80211_hw *hw, diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 2d46d4af60d7..d526f2fe9fe5 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -3181,7 +3181,7 @@ static void ieee80211_enable_ps(struct ieee80211_local *local, return; conf->flags |= IEEE80211_CONF_PS; - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); + ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS); } } @@ -3193,7 +3193,7 @@ static void ieee80211_change_ps(struct ieee80211_local *local) ieee80211_enable_ps(local, local->ps_sdata); } else if (conf->flags & IEEE80211_CONF_PS) { conf->flags &= ~IEEE80211_CONF_PS; - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); + ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS); timer_delete_sync(&local->dynamic_ps_timer); wiphy_work_cancel(local->hw.wiphy, &local->dynamic_ps_enable_work); @@ -3302,7 +3302,7 @@ void ieee80211_dynamic_ps_disable_work(struct wiphy *wiphy, if (local->hw.conf.flags & IEEE80211_CONF_PS) { local->hw.conf.flags &= ~IEEE80211_CONF_PS; - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); + ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS); } ieee80211_wake_queues_by_reason(&local->hw, @@ -3377,7 +3377,7 @@ void ieee80211_dynamic_ps_enable_work(struct wiphy *wiphy, (ifmgd->flags & IEEE80211_STA_NULLFUNC_ACKED)) { ifmgd->flags &= ~IEEE80211_STA_NULLFUNC_ACKED; local->hw.conf.flags |= IEEE80211_CONF_PS; - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); + ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS); } } @@ -3986,7 +3986,7 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata, */ if (local->hw.conf.flags & IEEE80211_CONF_PS) { local->hw.conf.flags &= ~IEEE80211_CONF_PS; - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); + ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS); } local->ps_sdata = NULL; @@ -7340,7 +7340,7 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link, if (local->hw.conf.dynamic_ps_timeout > 0) { if (local->hw.conf.flags & IEEE80211_CONF_PS) { local->hw.conf.flags &= ~IEEE80211_CONF_PS; - ieee80211_hw_config(local, + ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS); } ieee80211_send_nullfunc(local, sdata, false); diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 686d9f6e9b52..13df6321634d 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -39,7 +39,7 @@ static void ieee80211_offchannel_ps_enable(struct ieee80211_sub_if_data *sdata) if (local->hw.conf.flags & IEEE80211_CONF_PS) { offchannel_ps_enabled = true; local->hw.conf.flags &= ~IEEE80211_CONF_PS; - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); + ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS); } if (!offchannel_ps_enabled || diff --git a/net/mac80211/pm.c b/net/mac80211/pm.c index a9cc832240a5..5a508d99e84f 100644 --- a/net/mac80211/pm.c +++ b/net/mac80211/pm.c @@ -108,7 +108,7 @@ int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) sdata->u.mgd.powersave && !(local->hw.conf.flags & IEEE80211_CONF_PS)) { local->hw.conf.flags |= IEEE80211_CONF_PS; - ieee80211_hw_config(local, + ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS); } } diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h index 8215ca58ce5e..0bfbce157486 100644 --- a/net/mac80211/trace.h +++ b/net/mac80211/trace.h @@ -384,12 +384,14 @@ DEFINE_EVENT(local_sdata_addr_evt, drv_remove_interface, TRACE_EVENT(drv_config, TP_PROTO(struct ieee80211_local *local, + int radio_idx, u32 changed), - TP_ARGS(local, changed), + TP_ARGS(local, radio_idx, changed), TP_STRUCT__entry( LOCAL_ENTRY + __field(int, radio_idx) __field(u32, changed) __field(u32, flags) __field(int, power_level) @@ -403,6 +405,7 @@ TRACE_EVENT(drv_config, TP_fast_assign( LOCAL_ASSIGN; + __entry->radio_idx = radio_idx; __entry->changed = changed; __entry->flags = local->hw.conf.flags; __entry->power_level = local->hw.conf.power_level; @@ -417,8 +420,8 @@ TRACE_EVENT(drv_config, ), TP_printk( - LOCAL_PR_FMT " ch:%#x" CHANDEF_PR_FMT, - LOCAL_PR_ARG, __entry->changed, CHANDEF_PR_ARG + LOCAL_PR_FMT " radio_idx:%d ch:%#x" CHANDEF_PR_FMT, + LOCAL_PR_ARG, __entry->radio_idx, __entry->changed, CHANDEF_PR_ARG ) ); @@ -818,34 +821,71 @@ TRACE_EVENT(drv_get_key_seq, ) ); -DEFINE_EVENT(local_u32_evt, drv_set_frag_threshold, - TP_PROTO(struct ieee80211_local *local, u32 value), - TP_ARGS(local, value) +TRACE_EVENT(drv_set_frag_threshold, + TP_PROTO(struct ieee80211_local *local, int radio_idx, u32 value), + + TP_ARGS(local, radio_idx, value), + + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, radio_idx) + __field(u32, value) + ), + + TP_fast_assign( + LOCAL_ASSIGN; + __entry->radio_idx = radio_idx; + __entry->value = value; + ), + + TP_printk( + LOCAL_PR_FMT " radio_id:%d value:%u", + LOCAL_PR_ARG, __entry->radio_idx, __entry->value + ) ); -DEFINE_EVENT(local_u32_evt, drv_set_rts_threshold, - TP_PROTO(struct ieee80211_local *local, u32 value), - TP_ARGS(local, value) +TRACE_EVENT(drv_set_rts_threshold, + TP_PROTO(struct ieee80211_local *local, int radio_idx, u32 value), + + TP_ARGS(local, radio_idx, value), + + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, radio_idx) + __field(u32, value) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->radio_idx = radio_idx; + __entry->value = value; + ), + + TP_printk( + LOCAL_PR_FMT " radio_id:%d value:%u", + LOCAL_PR_ARG, __entry->radio_idx, __entry->value + ) ); TRACE_EVENT(drv_set_coverage_class, - TP_PROTO(struct ieee80211_local *local, s16 value), + TP_PROTO(struct ieee80211_local *local, int radio_idx, s16 value), - TP_ARGS(local, value), + TP_ARGS(local, radio_idx, value), TP_STRUCT__entry( LOCAL_ENTRY + __field(int, radio_idx) __field(s16, value) ), TP_fast_assign( LOCAL_ASSIGN; + __entry->radio_idx = radio_idx; __entry->value = value; ), TP_printk( - LOCAL_PR_FMT " value:%d", - LOCAL_PR_ARG, __entry->value + LOCAL_PR_FMT " radio_id:%d value:%d", + LOCAL_PR_ARG, __entry->radio_idx, __entry->value ) ); @@ -1318,12 +1358,14 @@ TRACE_EVENT(drv_set_antenna, ); TRACE_EVENT(drv_get_antenna, - TP_PROTO(struct ieee80211_local *local, u32 tx_ant, u32 rx_ant, int ret), + TP_PROTO(struct ieee80211_local *local, int radio_idx, u32 tx_ant, + u32 rx_ant, int ret), - TP_ARGS(local, tx_ant, rx_ant, ret), + TP_ARGS(local, radio_idx, tx_ant, rx_ant, ret), TP_STRUCT__entry( LOCAL_ENTRY + __field(int, radio_idx) __field(u32, tx_ant) __field(u32, rx_ant) __field(int, ret) @@ -1331,14 +1373,16 @@ TRACE_EVENT(drv_get_antenna, TP_fast_assign( LOCAL_ASSIGN; + __entry->radio_idx = radio_idx; __entry->tx_ant = tx_ant; __entry->rx_ant = rx_ant; __entry->ret = ret; ), TP_printk( - LOCAL_PR_FMT " tx_ant:%d rx_ant:%d ret:%d", - LOCAL_PR_ARG, __entry->tx_ant, __entry->rx_ant, __entry->ret + LOCAL_PR_FMT " radio_idx:%d tx_ant:%d rx_ant:%d ret:%d", + LOCAL_PR_ARG, __entry->radio_idx, __entry->tx_ant, + __entry->rx_ant, __entry->ret ) ); diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index d58b80813bdd..6278d55aeb2e 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -1541,7 +1541,7 @@ void ieee80211_txq_purge(struct ieee80211_local *local, spin_unlock_bh(&local->active_txq_lock[txqi->txq.ac]); } -void ieee80211_txq_set_params(struct ieee80211_local *local) +void ieee80211_txq_set_params(struct ieee80211_local *local, int radio_idx) { if (local->hw.wiphy->txq_limit) local->fq.limit = local->hw.wiphy->txq_limit; @@ -1605,7 +1605,7 @@ int ieee80211_txq_setup_flows(struct ieee80211_local *local) for (i = 0; i < fq->flows_cnt; i++) codel_vars_init(&local->cvars[i]); - ieee80211_txq_set_params(local); + ieee80211_txq_set_params(local, -1); return 0; } diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 24c43a1ef2aa..773c8da0acc9 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -1826,13 +1826,13 @@ int ieee80211_reconfig(struct ieee80211_local *local) } /* setup fragmentation threshold */ - drv_set_frag_threshold(local, hw->wiphy->frag_threshold); + drv_set_frag_threshold(local, -1, hw->wiphy->frag_threshold); /* setup RTS threshold */ - drv_set_rts_threshold(local, hw->wiphy->rts_threshold); + drv_set_rts_threshold(local, -1, hw->wiphy->rts_threshold); /* reset coverage class */ - drv_set_coverage_class(local, hw->wiphy->coverage_class); + drv_set_coverage_class(local, -1, hw->wiphy->coverage_class); ieee80211_led_radio(local, true); ieee80211_mod_tpt_led_trig(local, @@ -1890,11 +1890,11 @@ int ieee80211_reconfig(struct ieee80211_local *local) ieee80211_assign_chanctx(local, sdata, &sdata->deflink); /* reconfigure hardware */ - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_LISTEN_INTERVAL | - IEEE80211_CONF_CHANGE_MONITOR | - IEEE80211_CONF_CHANGE_PS | - IEEE80211_CONF_CHANGE_RETRY_LIMITS | - IEEE80211_CONF_CHANGE_IDLE); + ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_LISTEN_INTERVAL | + IEEE80211_CONF_CHANGE_MONITOR | + IEEE80211_CONF_CHANGE_PS | + IEEE80211_CONF_CHANGE_RETRY_LIMITS | + IEEE80211_CONF_CHANGE_IDLE); ieee80211_configure_filter(local); diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 9ef618baac9e..b40978549790 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -854,6 +854,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_MLO_RECONF_REM_LINKS] = { .type = NLA_U16 }, [NL80211_ATTR_EPCS] = { .type = NLA_FLAG }, [NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS] = { .type = NLA_U16 }, + [NL80211_ATTR_WIPHY_RADIO_INDEX] = { .type = NLA_U8 }, }; /* policy for the key attributes */ @@ -2639,7 +2640,7 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev, u32 tx_ant = 0, rx_ant = 0; int res; - res = rdev_get_antenna(rdev, &tx_ant, &rx_ant); + res = rdev_get_antenna(rdev, -1, &tx_ant, &rx_ant); if (!res) { if (nla_put_u32(msg, NL80211_ATTR_WIPHY_ANTENNA_TX, @@ -3620,6 +3621,7 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) u32 frag_threshold = 0, rts_threshold = 0; u8 coverage_class = 0; u32 txq_limit = 0, txq_memory_limit = 0, txq_quantum = 0; + int radio_idx = -1; rtnl_lock(); /* @@ -3670,6 +3672,17 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) if (result) return result; + if (info->attrs[NL80211_ATTR_WIPHY_RADIO_INDEX]) { + /* Radio idx is not expected for non-multi radio wiphy */ + if (rdev->wiphy.n_radio <= 0) + return -EINVAL; + + radio_idx = nla_get_u8( + info->attrs[NL80211_ATTR_WIPHY_RADIO_INDEX]); + if (radio_idx >= rdev->wiphy.n_radio) + return -EINVAL; + } + if (info->attrs[NL80211_ATTR_WIPHY_TXQ_PARAMS]) { struct ieee80211_txq_params txq_params; struct nlattr *tb[NL80211_TXQ_ATTR_MAX + 1]; @@ -3759,7 +3772,8 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) mbm = nla_get_u32(info->attrs[idx]); } - result = rdev_set_tx_power(rdev, txp_wdev, type, mbm); + result = rdev_set_tx_power(rdev, txp_wdev, radio_idx, type, + mbm); if (result) return result; } @@ -3785,7 +3799,7 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) tx_ant = tx_ant & rdev->wiphy.available_antennas_tx; rx_ant = rx_ant & rdev->wiphy.available_antennas_rx; - result = rdev_set_antenna(rdev, tx_ant, rx_ant); + result = rdev_set_antenna(rdev, radio_idx, tx_ant, rx_ant); if (result) return result; } @@ -3911,7 +3925,7 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) if (changed & WIPHY_PARAM_TXQ_QUANTUM) rdev->wiphy.txq_quantum = txq_quantum; - result = rdev_set_wiphy_params(rdev, changed); + result = rdev_set_wiphy_params(rdev, radio_idx, changed); if (result) { rdev->wiphy.retry_short = old_retry_short; rdev->wiphy.retry_long = old_retry_long; @@ -4012,7 +4026,7 @@ static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flag if (rdev->ops->get_tx_power && !wdev->valid_links) { int dbm, ret; - ret = rdev_get_tx_power(rdev, wdev, 0, &dbm); + ret = rdev_get_tx_power(rdev, wdev, -1, 0, &dbm); if (ret == 0 && nla_put_u32(msg, NL80211_ATTR_WIPHY_TX_POWER_LEVEL, DBM_TO_MBM(dbm))) @@ -4084,7 +4098,7 @@ static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flag if (rdev->ops->get_tx_power) { int dbm, ret; - ret = rdev_get_tx_power(rdev, wdev, link_id, &dbm); + ret = rdev_get_tx_power(rdev, wdev, -1, link_id, &dbm); if (ret == 0 && nla_put_u32(msg, NL80211_ATTR_WIPHY_TX_POWER_LEVEL, DBM_TO_MBM(dbm))) diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index 9f4783c2354c..803b39c26587 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -577,35 +577,40 @@ static inline int rdev_leave_ibss(struct cfg80211_registered_device *rdev, } static inline int -rdev_set_wiphy_params(struct cfg80211_registered_device *rdev, u32 changed) +rdev_set_wiphy_params(struct cfg80211_registered_device *rdev, int radio_idx, + u32 changed) { int ret = -EOPNOTSUPP; - trace_rdev_set_wiphy_params(&rdev->wiphy, changed); + trace_rdev_set_wiphy_params(&rdev->wiphy, radio_idx, changed); if (rdev->ops->set_wiphy_params) - ret = rdev->ops->set_wiphy_params(&rdev->wiphy, changed); + ret = rdev->ops->set_wiphy_params(&rdev->wiphy, radio_idx, + changed); trace_rdev_return_int(&rdev->wiphy, ret); return ret; } static inline int rdev_set_tx_power(struct cfg80211_registered_device *rdev, - struct wireless_dev *wdev, - enum nl80211_tx_power_setting type, int mbm) + struct wireless_dev *wdev, int radio_idx, + enum nl80211_tx_power_setting type, + int mbm) { int ret; - trace_rdev_set_tx_power(&rdev->wiphy, wdev, type, mbm); - ret = rdev->ops->set_tx_power(&rdev->wiphy, wdev, type, mbm); + trace_rdev_set_tx_power(&rdev->wiphy, wdev, radio_idx, type, mbm); + ret = rdev->ops->set_tx_power(&rdev->wiphy, wdev, radio_idx, type, + mbm); trace_rdev_return_int(&rdev->wiphy, ret); return ret; } static inline int rdev_get_tx_power(struct cfg80211_registered_device *rdev, - struct wireless_dev *wdev, unsigned int link_id, - int *dbm) + struct wireless_dev *wdev, int radio_idx, + unsigned int link_id, int *dbm) { int ret; - trace_rdev_get_tx_power(&rdev->wiphy, wdev, link_id); - ret = rdev->ops->get_tx_power(&rdev->wiphy, wdev, link_id, dbm); + trace_rdev_get_tx_power(&rdev->wiphy, wdev, radio_idx, link_id); + ret = rdev->ops->get_tx_power(&rdev->wiphy, wdev, radio_idx, link_id, + dbm); trace_rdev_return_int_int(&rdev->wiphy, ret, *dbm); return ret; } @@ -857,21 +862,21 @@ rdev_update_mgmt_frame_registrations(struct cfg80211_registered_device *rdev, } static inline int rdev_set_antenna(struct cfg80211_registered_device *rdev, - u32 tx_ant, u32 rx_ant) + int radio_idx, u32 tx_ant, u32 rx_ant) { int ret; - trace_rdev_set_antenna(&rdev->wiphy, tx_ant, rx_ant); - ret = rdev->ops->set_antenna(&rdev->wiphy, tx_ant, rx_ant); + trace_rdev_set_antenna(&rdev->wiphy, radio_idx, tx_ant, rx_ant); + ret = rdev->ops->set_antenna(&rdev->wiphy, -1, tx_ant, rx_ant); trace_rdev_return_int(&rdev->wiphy, ret); return ret; } static inline int rdev_get_antenna(struct cfg80211_registered_device *rdev, - u32 *tx_ant, u32 *rx_ant) + int radio_idx, u32 *tx_ant, u32 *rx_ant) { int ret; - trace_rdev_get_antenna(&rdev->wiphy); - ret = rdev->ops->get_antenna(&rdev->wiphy, tx_ant, rx_ant); + trace_rdev_get_antenna(&rdev->wiphy, radio_idx); + ret = rdev->ops->get_antenna(&rdev->wiphy, radio_idx, tx_ant, rx_ant); if (ret) trace_rdev_return_int(&rdev->wiphy, ret); else diff --git a/net/wireless/trace.h b/net/wireless/trace.h index 61a5eca9c513..7e43ab9de923 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -406,9 +406,19 @@ DEFINE_EVENT(wiphy_only_evt, rdev_return_void, TP_ARGS(wiphy) ); -DEFINE_EVENT(wiphy_only_evt, rdev_get_antenna, - TP_PROTO(struct wiphy *wiphy), - TP_ARGS(wiphy) +TRACE_EVENT(rdev_get_antenna, + TP_PROTO(struct wiphy *wiphy, int radio_idx), + TP_ARGS(wiphy, radio_idx), + TP_STRUCT__entry( + WIPHY_ENTRY + __field(int, radio_idx) + ), + TP_fast_assign( + WIPHY_ASSIGN; + __entry->radio_idx = radio_idx; + ), + TP_printk(WIPHY_PR_FMT ", radio_idx: %d", + WIPHY_PR_ARG, __entry->radio_idx) ); DEFINE_EVENT(wiphy_only_evt, rdev_rfkill_poll, @@ -1678,18 +1688,20 @@ TRACE_EVENT(rdev_join_ocb, ); TRACE_EVENT(rdev_set_wiphy_params, - TP_PROTO(struct wiphy *wiphy, u32 changed), - TP_ARGS(wiphy, changed), + TP_PROTO(struct wiphy *wiphy, int radio_idx, u32 changed), + TP_ARGS(wiphy, radio_idx, changed), TP_STRUCT__entry( WIPHY_ENTRY + __field(int, radio_idx) __field(u32, changed) ), TP_fast_assign( WIPHY_ASSIGN; + __entry->radio_idx = radio_idx; __entry->changed = changed; ), - TP_printk(WIPHY_PR_FMT ", changed: %u", - WIPHY_PR_ARG, __entry->changed) + TP_printk(WIPHY_PR_FMT ", radio_idx: %d, changed: %u", + WIPHY_PR_ARG, __entry->radio_idx, __entry->changed) ); DECLARE_EVENT_CLASS(wiphy_wdev_link_evt, @@ -1710,30 +1722,51 @@ DECLARE_EVENT_CLASS(wiphy_wdev_link_evt, WIPHY_PR_ARG, WDEV_PR_ARG, __entry->link_id) ); -DEFINE_EVENT(wiphy_wdev_link_evt, rdev_get_tx_power, +TRACE_EVENT(rdev_get_tx_power, TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev, - unsigned int link_id), - TP_ARGS(wiphy, wdev, link_id) + int radio_idx, unsigned int link_id), + TP_ARGS(wiphy, wdev, radio_idx, link_id), + TP_STRUCT__entry( + WIPHY_ENTRY + WDEV_ENTRY + __field(int, radio_idx) + __field(unsigned int, link_id) + ), + TP_fast_assign( + WIPHY_ASSIGN; + WDEV_ASSIGN; + __entry->radio_idx = radio_idx; + __entry->link_id = link_id; + ), + TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT + ", radio_idx: %d, link_id: %u", + WIPHY_PR_ARG, WDEV_PR_ARG, + __entry->radio_idx, __entry->link_id) ); TRACE_EVENT(rdev_set_tx_power, TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev, - enum nl80211_tx_power_setting type, int mbm), - TP_ARGS(wiphy, wdev, type, mbm), + int radio_idx, enum nl80211_tx_power_setting type, + int mbm), + TP_ARGS(wiphy, wdev, radio_idx, type, mbm), TP_STRUCT__entry( WIPHY_ENTRY WDEV_ENTRY + __field(int, radio_idx) __field(enum nl80211_tx_power_setting, type) __field(int, mbm) ), TP_fast_assign( WIPHY_ASSIGN; WDEV_ASSIGN; + __entry->radio_idx = radio_idx; __entry->type = type; __entry->mbm = mbm; ), - TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT ", type: %u, mbm: %d", - WIPHY_PR_ARG, WDEV_PR_ARG,__entry->type, __entry->mbm) + TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT + ", radio_idx: %d, type: %u, mbm: %d", + WIPHY_PR_ARG, WDEV_PR_ARG, + __entry->radio_idx, __entry->type, __entry->mbm) ); TRACE_EVENT(rdev_return_int_int, @@ -1866,26 +1899,24 @@ TRACE_EVENT(rdev_return_void_tx_rx, __entry->rx_max) ); -DECLARE_EVENT_CLASS(tx_rx_evt, - TP_PROTO(struct wiphy *wiphy, u32 tx, u32 rx), - TP_ARGS(wiphy, tx, rx), +TRACE_EVENT(rdev_set_antenna, + TP_PROTO(struct wiphy *wiphy, int radio_idx, u32 tx, u32 rx), + TP_ARGS(wiphy, radio_idx, tx, rx), TP_STRUCT__entry( WIPHY_ENTRY + __field(int, radio_idx) __field(u32, tx) __field(u32, rx) ), TP_fast_assign( WIPHY_ASSIGN; + __entry->radio_idx = radio_idx; __entry->tx = tx; __entry->rx = rx; ), - TP_printk(WIPHY_PR_FMT ", tx: %u, rx: %u ", - WIPHY_PR_ARG, __entry->tx, __entry->rx) -); - -DEFINE_EVENT(tx_rx_evt, rdev_set_antenna, - TP_PROTO(struct wiphy *wiphy, u32 tx, u32 rx), - TP_ARGS(wiphy, tx, rx) + TP_printk(WIPHY_PR_FMT ", radio_idx: %d, tx: %u, rx: %u ", + WIPHY_PR_ARG, __entry->radio_idx, + __entry->tx, __entry->rx) ); DECLARE_EVENT_CLASS(wiphy_netdev_id_evt, diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c index a74b1afc594e..1241fda78a68 100644 --- a/net/wireless/wext-compat.c +++ b/net/wireless/wext-compat.c @@ -263,7 +263,7 @@ int cfg80211_wext_siwrts(struct net_device *dev, else wdev->wiphy->rts_threshold = rts->value; - err = rdev_set_wiphy_params(rdev, WIPHY_PARAM_RTS_THRESHOLD); + err = rdev_set_wiphy_params(rdev, -1, WIPHY_PARAM_RTS_THRESHOLD); if (err) wdev->wiphy->rts_threshold = orts; return err; @@ -304,7 +304,7 @@ int cfg80211_wext_siwfrag(struct net_device *dev, wdev->wiphy->frag_threshold = frag->value & ~0x1; } - err = rdev_set_wiphy_params(rdev, WIPHY_PARAM_FRAG_THRESHOLD); + err = rdev_set_wiphy_params(rdev, -1, WIPHY_PARAM_FRAG_THRESHOLD); if (err) wdev->wiphy->frag_threshold = ofrag; return err; @@ -355,7 +355,7 @@ static int cfg80211_wext_siwretry(struct net_device *dev, changed |= WIPHY_PARAM_RETRY_SHORT; } - err = rdev_set_wiphy_params(rdev, changed); + err = rdev_set_wiphy_params(rdev, -1, changed); if (err) { wdev->wiphy->retry_short = oshort; wdev->wiphy->retry_long = olong; @@ -890,7 +890,7 @@ static int cfg80211_wext_siwtxpower(struct net_device *dev, guard(wiphy)(&rdev->wiphy); - return rdev_set_tx_power(rdev, wdev, type, DBM_TO_MBM(dbm)); + return rdev_set_tx_power(rdev, wdev, -1, type, DBM_TO_MBM(dbm)); } static int cfg80211_wext_giwtxpower(struct net_device *dev, @@ -910,7 +910,7 @@ static int cfg80211_wext_giwtxpower(struct net_device *dev, return -EOPNOTSUPP; scoped_guard(wiphy, &rdev->wiphy) { - err = rdev_get_tx_power(rdev, wdev, 0, &val); + err = rdev_get_tx_power(rdev, wdev, -1, 0, &val); } if (err) return err; -- cgit v1.2.3 From 89595190058c6e9ca4a8ca7d49be3fc8d2395e79 Mon Sep 17 00:00:00 2001 From: Roopni Devanathan Date: Sun, 15 Jun 2025 13:53:11 +0530 Subject: wifi: cfg80211: Report per-radio RTS threshold to userspace In case of multi-radio wiphys, with per-radio RTS threshold brought into use, RTS threshold for each radio in a wiphy can be recorded in wiphy parameter - wiphy_radio_cfg, as an array. Add a new attribute - NL80211_WIPHY_RADIO_ATTR_RTS_THRESHOLD in nested parameter - NL80211_ATTR_WIPHY_RADIOS. When a request for getting RTS threshold for a particular radio is received, parse the radio id and get the required data. Add this data to the newly added nested attribute NL80211_WIPHY_RADIO_ATTR_RTS_THRESHOLD. Add support to report this data to userspace. Signed-off-by: Roopni Devanathan Link: https://patch.msgid.link/20250615082312.619639-4-quic_rdevanat@quicinc.com Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 2 ++ net/wireless/nl80211.c | 6 ++++++ 2 files changed, 8 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 2a71149c3065..39460334dafb 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -8106,6 +8106,7 @@ enum nl80211_ap_settings_flags { * and contains attributes defined in &enum nl80211_if_combination_attrs. * @NL80211_WIPHY_RADIO_ATTR_ANTENNA_MASK: bitmask (u32) of antennas * connected to this radio. + * @NL80211_WIPHY_RADIO_ATTR_RTS_THRESHOLD: RTS threshold (u32) of this radio. * * @__NL80211_WIPHY_RADIO_ATTR_LAST: Internal * @NL80211_WIPHY_RADIO_ATTR_MAX: Highest attribute @@ -8117,6 +8118,7 @@ enum nl80211_wiphy_radio_attrs { NL80211_WIPHY_RADIO_ATTR_FREQ_RANGE, NL80211_WIPHY_RADIO_ATTR_INTERFACE_COMBINATION, NL80211_WIPHY_RADIO_ATTR_ANTENNA_MASK, + NL80211_WIPHY_RADIO_ATTR_RTS_THRESHOLD, /* keep last */ __NL80211_WIPHY_RADIO_ATTR_LAST, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index b0176090182c..70bfe2bfdcc7 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2447,6 +2447,7 @@ fail: static int nl80211_put_radio(struct wiphy *wiphy, struct sk_buff *msg, int idx) { const struct wiphy_radio *r = &wiphy->radio[idx]; + const struct wiphy_radio_cfg *rcfg = &wiphy->radio_cfg[idx]; struct nlattr *radio, *freq; int i; @@ -2457,6 +2458,11 @@ static int nl80211_put_radio(struct wiphy *wiphy, struct sk_buff *msg, int idx) if (nla_put_u32(msg, NL80211_WIPHY_RADIO_ATTR_INDEX, idx)) goto nla_put_failure; + if (rcfg->rts_threshold && + nla_put_u32(msg, NL80211_WIPHY_RADIO_ATTR_RTS_THRESHOLD, + rcfg->rts_threshold)) + goto nla_put_failure; + if (r->antenna_mask && nla_put_u32(msg, NL80211_WIPHY_RADIO_ATTR_ANTENNA_MASK, r->antenna_mask)) -- cgit v1.2.3 From 826334359eacc1b70e9752ebc4954ed775dd40ca Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 23 Jun 2025 16:17:13 -0700 Subject: netlink: specs: add the multicast group name to spec Add the multicast group's name to the YAML spec. Without it YNL doesn't know how to subscribe to notifications. Reviewed-by: Donald Hunter Link: https://patch.msgid.link/20250623231720.3124717-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/ethtool.yaml | 6 ++++++ include/uapi/linux/ethtool_netlink.h | 2 -- include/uapi/linux/ethtool_netlink_generated.h | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index c1651e175e8b..cfe84f84ba29 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -2492,3 +2492,9 @@ operations: attributes: - header - events + +mcast-groups: + list: + - + name: monitor + c-define-name: ethtool-mcgrp-monitor-name diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h index 09a75bdb6560..fa5d645140a4 100644 --- a/include/uapi/linux/ethtool_netlink.h +++ b/include/uapi/linux/ethtool_netlink.h @@ -208,6 +208,4 @@ enum { ETHTOOL_A_STATS_PHY_MAX = (__ETHTOOL_A_STATS_PHY_CNT - 1) }; -#define ETHTOOL_MCGRP_MONITOR_NAME "monitor" - #endif /* _UAPI_LINUX_ETHTOOL_NETLINK_H_ */ diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 4944badf9fba..859e28c8a91a 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -867,4 +867,6 @@ enum { ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1) }; +#define ETHTOOL_MCGRP_MONITOR_NAME "monitor" + #endif /* _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H */ -- cgit v1.2.3 From 46837be5afc6ea70bc827ca4439410e069e2ee37 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 23 Jun 2025 16:17:18 -0700 Subject: net: ethtool: rss: add notifications In preparation for RSS_SET handling in ethnl introduce Netlink notifications for RSS. Only cover modifications, not creation and not removal of a context, because the latter may deserve a different notification type. We should cross that bridge when we add the support for context add / remove via Netlink. Link: https://patch.msgid.link/20250623231720.3124717-7-kuba@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/ethtool.yaml | 7 +++++++ Documentation/networking/ethtool-netlink.rst | 1 + include/uapi/linux/ethtool_netlink_generated.h | 1 + net/ethtool/common.h | 8 ++++++++ net/ethtool/ioctl.c | 4 ++++ net/ethtool/netlink.c | 2 ++ net/ethtool/rss.c | 11 +++++++++++ 7 files changed, 34 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index cfe84f84ba29..19a32229772a 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -2492,6 +2492,13 @@ operations: attributes: - header - events + - + name: rss-ntf + doc: | + Notification for change in RSS configuration. + For additional contexts only modifications are modified, not creation + or removal of the contexts. + notify: rss-get mcast-groups: list: diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index e45bb555e909..08abca99a6dc 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -281,6 +281,7 @@ Kernel to userspace: ``ETHTOOL_MSG_MODULE_GET_REPLY`` transceiver module parameters ``ETHTOOL_MSG_PSE_GET_REPLY`` PSE parameters ``ETHTOOL_MSG_RSS_GET_REPLY`` RSS settings + ``ETHTOOL_MSG_RSS_NTF`` RSS settings ``ETHTOOL_MSG_PLCA_GET_CFG_REPLY`` PLCA RS parameters ``ETHTOOL_MSG_PLCA_GET_STATUS_REPLY`` PLCA RS status ``ETHTOOL_MSG_PLCA_NTF`` PLCA RS parameters diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 859e28c8a91a..8f30ffa1cd14 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -862,6 +862,7 @@ enum { ETHTOOL_MSG_TSCONFIG_GET_REPLY, ETHTOOL_MSG_TSCONFIG_SET_REPLY, ETHTOOL_MSG_PSE_NTF, + ETHTOOL_MSG_RSS_NTF, __ETHTOOL_MSG_KERNEL_CNT, ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1) diff --git a/net/ethtool/common.h b/net/ethtool/common.h index b4683d286a5a..c41db1595621 100644 --- a/net/ethtool/common.h +++ b/net/ethtool/common.h @@ -74,4 +74,12 @@ int ethtool_get_module_eeprom_call(struct net_device *dev, bool __ethtool_dev_mm_supported(struct net_device *dev); +#if IS_ENABLED(CONFIG_ETHTOOL_NETLINK) +void ethtool_rss_notify(struct net_device *dev, u32 rss_context); +#else +static inline void ethtool_rss_notify(struct net_device *dev, u32 rss_context) +{ +} +#endif + #endif /* _ETHTOOL_COMMON_H */ diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index 96da9d18789b..c34bac7bffd8 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -1502,6 +1502,7 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev, struct ethtool_rxfh rxfh; bool locked = false; /* dev->ethtool->rss_lock taken */ bool create = false; + bool mod = false; u8 *rss_config; int ret; @@ -1688,6 +1689,7 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev, } goto out; } + mod = !create && !rxfh_dev.rss_delete; if (copy_to_user(useraddr + offsetof(struct ethtool_rxfh, rss_context), &rxfh_dev.rss_context, sizeof(rxfh_dev.rss_context))) @@ -1757,6 +1759,8 @@ out: if (locked) mutex_unlock(&dev->ethtool->rss_lock); kfree(rss_config); + if (mod) + ethtool_rss_notify(dev, rxfh.rss_context); return ret; } diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index 60b3c07507d2..09c81cc9a08f 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -946,6 +946,7 @@ ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = { [ETHTOOL_MSG_MODULE_NTF] = ðnl_module_request_ops, [ETHTOOL_MSG_PLCA_NTF] = ðnl_plca_cfg_request_ops, [ETHTOOL_MSG_MM_NTF] = ðnl_mm_request_ops, + [ETHTOOL_MSG_RSS_NTF] = ðnl_rss_request_ops, }; /* default notification handler */ @@ -1052,6 +1053,7 @@ static const ethnl_notify_handler_t ethnl_notify_handlers[] = { [ETHTOOL_MSG_MODULE_NTF] = ethnl_default_notify, [ETHTOOL_MSG_PLCA_NTF] = ethnl_default_notify, [ETHTOOL_MSG_MM_NTF] = ethnl_default_notify, + [ETHTOOL_MSG_RSS_NTF] = ethnl_default_notify, }; void ethnl_notify(struct net_device *dev, unsigned int cmd, diff --git a/net/ethtool/rss.c b/net/ethtool/rss.c index 6d9b1769896b..3adddca7e215 100644 --- a/net/ethtool/rss.c +++ b/net/ethtool/rss.c @@ -358,6 +358,17 @@ int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb) return ret; } +/* RSS_NTF */ + +void ethtool_rss_notify(struct net_device *dev, u32 rss_context) +{ + struct rss_req_info req_info = { + .rss_context = rss_context, + }; + + ethnl_notify(dev, ETHTOOL_MSG_RSS_NTF, &req_info.base); +} + const struct ethnl_request_ops ethnl_rss_request_ops = { .request_cmd = ETHTOOL_MSG_RSS_GET, .reply_cmd = ETHTOOL_MSG_RSS_GET_REPLY, -- cgit v1.2.3 From 2855e43c6bb154a9b8e27abda8df364aed574b22 Mon Sep 17 00:00:00 2001 From: RubenKelevra Date: Tue, 24 Jun 2025 18:57:11 +0200 Subject: uapi: net_dropmon: drop unused is_drop_point_hw macro Commit 4ea7e38696c7 ("dropmon: add ability to detect when hardware drops rx packets") introduced is_drop_point_hw, but the symbol was never referenced anywhere in the kernel tree and is currently not used by dropwatch. I could not find, to the best of my abilities, a current out-of-tree user of this macro. The definition also contains a syntax error in its for-loop, so any project that tried to compile against it would fail. Removing the macro therefore eliminates dead code without breaking existing users. Signed-off-by: RubenKelevra Link: https://patch.msgid.link/20250624165711.1188691-1-rubenkelevra@gmail.com Signed-off-by: Jakub Kicinski --- include/uapi/linux/net_dropmon.h | 7 ------- 1 file changed, 7 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/net_dropmon.h b/include/uapi/linux/net_dropmon.h index 9dd41c2f58a6..87cbef48d4c7 100644 --- a/include/uapi/linux/net_dropmon.h +++ b/include/uapi/linux/net_dropmon.h @@ -10,13 +10,6 @@ struct net_dm_drop_point { __u32 count; }; -#define is_drop_point_hw(x) do {\ - int ____i, ____j;\ - for (____i = 0; ____i < 8; i ____i++)\ - ____j |= x[____i];\ - ____j;\ -} while (0) - #define NET_DM_CFG_VERSION 0 #define NET_DM_CFG_ALERT_COUNT 1 #define NET_DM_CFG_ALERT_DELAY 2 -- cgit v1.2.3 From 7f15ee35972dd3dee37704bfd0f136290f6d63d9 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kubalewski Date: Thu, 26 Jun 2025 15:52:17 +0200 Subject: dpll: add reference-sync netlink attribute Add new netlink attribute to allow user space configuration of reference sync pin pairs, where both pins are used to provide one clock signal consisting of both: base frequency and sync signal. Reviewed-by: Przemek Kitszel Reviewed-by: Milena Olech Reviewed-by: Jiri Pirko Signed-off-by: Arkadiusz Kubalewski Link: https://patch.msgid.link/20250626135219.1769350-2-arkadiusz.kubalewski@intel.com Signed-off-by: Jakub Kicinski --- Documentation/driver-api/dpll.rst | 25 +++++++++++++++++++++++++ Documentation/netlink/specs/dpll.yaml | 19 +++++++++++++++++++ drivers/dpll/dpll_nl.c | 10 ++++++++-- drivers/dpll/dpll_nl.h | 1 + include/uapi/linux/dpll.h | 1 + 5 files changed, 54 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index 195e1e5d9a58..eca72d9b9ed8 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -253,6 +253,31 @@ the pin. ``DPLL_A_PIN_ESYNC_PULSE`` pulse type of Embedded SYNC ========================================= ================================= +Reference SYNC +============== + +The device may support the Reference SYNC feature, which allows the combination +of two inputs into a input pair. In this configuration, clock signals +from both inputs are used to synchronize the DPLL device. The higher frequency +signal is utilized for the loop bandwidth of the DPLL, while the lower frequency +signal is used to syntonize the output signal of the DPLL device. This feature +enables the provision of a high-quality loop bandwidth signal from an external +source. + +A capable input provides a list of inputs that can be bound with to create +Reference SYNC. To control this feature, the user must request a desired +state for a target pin: use ``DPLL_PIN_STATE_CONNECTED`` to enable or +``DPLL_PIN_STATE_DISCONNECTED`` to disable the feature. An input pin can be +bound to only one other pin at any given time. + + ============================== ========================================== + ``DPLL_A_PIN_REFERENCE_SYNC`` nested attribute for providing info or + requesting configuration of the Reference + SYNC feature + ``DPLL_A_PIN_ID`` target pin id for Reference SYNC feature + ``DPLL_A_PIN_STATE`` state of Reference SYNC connection + ============================== ========================================== + Configuration commands group ============================ diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index c13440efab24..5decee61a2c4 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -428,6 +428,15 @@ attribute-sets: doc: | A ratio of high to low state of a SYNC signal pulse embedded into base clock frequency. Value is in percents. + - + name: reference-sync + type: nest + multi-attr: true + nested-attributes: reference-sync + doc: | + Capable pin provides list of pins that can be bound to create a + reference-sync pin pair. + - name: pin-parent-device subset-of: pin @@ -458,6 +467,14 @@ attribute-sets: name: frequency-min - name: frequency-max + - + name: reference-sync + subset-of: pin + attributes: + - + name: id + - + name: state operations: enum-name: dpll_cmd @@ -598,6 +615,7 @@ operations: - esync-frequency - esync-frequency-supported - esync-pulse + - reference-sync dump: request: @@ -625,6 +643,7 @@ operations: - parent-pin - phase-adjust - esync-frequency + - reference-sync - name: pin-create-ntf doc: Notification about pin appearing diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index 8de90310c3be..9f2efaf25268 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -24,6 +24,11 @@ const struct nla_policy dpll_pin_parent_pin_nl_policy[DPLL_A_PIN_STATE + 1] = { [DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3), }; +const struct nla_policy dpll_reference_sync_nl_policy[DPLL_A_PIN_STATE + 1] = { + [DPLL_A_PIN_ID] = { .type = NLA_U32, }, + [DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3), +}; + /* DPLL_CMD_DEVICE_ID_GET - do */ static const struct nla_policy dpll_device_id_get_nl_policy[DPLL_A_TYPE + 1] = { [DPLL_A_MODULE_NAME] = { .type = NLA_NUL_STRING, }, @@ -63,7 +68,7 @@ static const struct nla_policy dpll_pin_get_dump_nl_policy[DPLL_A_PIN_ID + 1] = }; /* DPLL_CMD_PIN_SET - do */ -static const struct nla_policy dpll_pin_set_nl_policy[DPLL_A_PIN_ESYNC_FREQUENCY + 1] = { +static const struct nla_policy dpll_pin_set_nl_policy[DPLL_A_PIN_REFERENCE_SYNC + 1] = { [DPLL_A_PIN_ID] = { .type = NLA_U32, }, [DPLL_A_PIN_FREQUENCY] = { .type = NLA_U64, }, [DPLL_A_PIN_DIRECTION] = NLA_POLICY_RANGE(NLA_U32, 1, 2), @@ -73,6 +78,7 @@ static const struct nla_policy dpll_pin_set_nl_policy[DPLL_A_PIN_ESYNC_FREQUENCY [DPLL_A_PIN_PARENT_PIN] = NLA_POLICY_NESTED(dpll_pin_parent_pin_nl_policy), [DPLL_A_PIN_PHASE_ADJUST] = { .type = NLA_S32, }, [DPLL_A_PIN_ESYNC_FREQUENCY] = { .type = NLA_U64, }, + [DPLL_A_PIN_REFERENCE_SYNC] = NLA_POLICY_NESTED(dpll_reference_sync_nl_policy), }; /* Ops table for dpll */ @@ -140,7 +146,7 @@ static const struct genl_split_ops dpll_nl_ops[] = { .doit = dpll_nl_pin_set_doit, .post_doit = dpll_pin_post_doit, .policy = dpll_pin_set_nl_policy, - .maxattr = DPLL_A_PIN_ESYNC_FREQUENCY, + .maxattr = DPLL_A_PIN_REFERENCE_SYNC, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, }; diff --git a/drivers/dpll/dpll_nl.h b/drivers/dpll/dpll_nl.h index f491262bee4f..3da10cfe9a6e 100644 --- a/drivers/dpll/dpll_nl.h +++ b/drivers/dpll/dpll_nl.h @@ -14,6 +14,7 @@ /* Common nested types */ extern const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_PHASE_OFFSET + 1]; extern const struct nla_policy dpll_pin_parent_pin_nl_policy[DPLL_A_PIN_STATE + 1]; +extern const struct nla_policy dpll_reference_sync_nl_policy[DPLL_A_PIN_STATE + 1]; int dpll_lock_doit(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 349e1b3ca1ae..37b438ce8efc 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -249,6 +249,7 @@ enum dpll_a_pin { DPLL_A_PIN_ESYNC_FREQUENCY, DPLL_A_PIN_ESYNC_FREQUENCY_SUPPORTED, DPLL_A_PIN_ESYNC_PULSE, + DPLL_A_PIN_REFERENCE_SYNC, __DPLL_A_PIN_MAX, DPLL_A_PIN_MAX = (__DPLL_A_PIN_MAX - 1) -- cgit v1.2.3 From 03dc03fa0432a9160c4fcbdb86f274e6b4587972 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Thu, 26 Jun 2025 10:31:10 +0300 Subject: neighbor: Add NTF_EXT_VALIDATED flag for externally validated entries tl;dr ===== Add a new neighbor flag ("extern_valid") that can be used to indicate to the kernel that a neighbor entry was learned and determined to be valid externally. The kernel will not try to remove or invalidate such an entry, leaving these decisions to the user space control plane. This is needed for EVPN multi-homing where a neighbor entry for a multi-homed host needs to be synced across all the VTEPs among which the host is multi-homed. Background ========== In a typical EVPN multi-homing setup each host is multi-homed using a set of links called ES (Ethernet Segment, i.e., LAG) to multiple leaf switches (VTEPs). VTEPs that are connected to the same ES are called ES peers. When a neighbor entry is learned on a VTEP, it is distributed to both ES peers and remote VTEPs using EVPN MAC/IP advertisement routes. ES peers use the neighbor entry when routing traffic towards the multi-homed host and remote VTEPs use it for ARP/NS suppression. Motivation ========== If the ES link between a host and the VTEP on which the neighbor entry was locally learned goes down, the EVPN MAC/IP advertisement route will be withdrawn and the neighbor entries will be removed from both ES peers and remote VTEPs. Routing towards the multi-homed host and ARP/NS suppression can fail until another ES peer locally learns the neighbor entry and distributes it via an EVPN MAC/IP advertisement route. "draft-rbickhart-evpn-ip-mac-proxy-adv-03" [1] suggests avoiding these intermittent failures by having the ES peers install the neighbor entries as before, but also injecting EVPN MAC/IP advertisement routes with a proxy indication. When the previously mentioned ES link goes down and the original EVPN MAC/IP advertisement route is withdrawn, the ES peers will not withdraw their neighbor entries, but instead start aging timers for the proxy indication. If an ES peer locally learns the neighbor entry (i.e., it becomes "reachable"), it will restart its aging timer for the entry and emit an EVPN MAC/IP advertisement route without a proxy indication. An ES peer will stop its aging timer for the proxy indication if it observes the removal of the proxy indication from at least one of the ES peers advertising the entry. In the event that the aging timer for the proxy indication expired, an ES peer will withdraw its EVPN MAC/IP advertisement route. If the timer expired on all ES peers and they all withdrew their proxy advertisements, the neighbor entry will be completely removed from the EVPN fabric. Implementation ============== In the above scheme, when the control plane (e.g., FRR) advertises a neighbor entry with a proxy indication, it expects the corresponding entry in the data plane (i.e., the kernel) to remain valid and not be removed due to garbage collection or loss of carrier. The control plane also expects the kernel to notify it if the entry was learned locally (i.e., became "reachable") so that it will remove the proxy indication from the EVPN MAC/IP advertisement route. That is why these entries cannot be programmed with dummy states such as "permanent" or "noarp". Instead, add a new neighbor flag ("extern_valid") which indicates that the entry was learned and determined to be valid externally and should not be removed or invalidated by the kernel. The kernel can probe the entry and notify user space when it becomes "reachable" (it is initially installed as "stale"). However, if the kernel does not receive a confirmation, have it return the entry to the "stale" state instead of the "failed" state. In other words, an entry marked with the "extern_valid" flag behaves like any other dynamically learned entry other than the fact that the kernel cannot remove or invalidate it. One can argue that the "extern_valid" flag should not prevent garbage collection and that instead a neighbor entry should be programmed with both the "extern_valid" and "extern_learn" flags. There are two reasons for not doing that: 1. Unclear why a control plane would like to program an entry that the kernel cannot invalidate but can completely remove. 2. The "extern_learn" flag is used by FRR for neighbor entries learned on remote VTEPs (for ARP/NS suppression) whereas here we are concerned with local entries. This distinction is currently irrelevant for the kernel, but might be relevant in the future. Given that the flag only makes sense when the neighbor has a valid state, reject attempts to add a neighbor with an invalid state and with this flag set. For example: # ip neigh add 192.0.2.1 nud none dev br0.10 extern_valid Error: Cannot create externally validated neighbor with an invalid state. # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid # ip neigh replace 192.0.2.1 nud failed dev br0.10 extern_valid Error: Cannot mark neighbor as externally validated with an invalid state. The above means that a neighbor cannot be created with the "extern_valid" flag and flags such as "use" or "managed" as they result in a neighbor being created with an invalid state ("none") and immediately getting probed: # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use Error: Cannot create externally validated neighbor with an invalid state. However, these flags can be used together with "extern_valid" after the neighbor was created with a valid state: # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use One consequence of preventing the kernel from invalidating a neighbor entry is that by default it will only try to determine reachability using unicast probes. This can be changed using the "mcast_resolicit" sysctl: # sysctl net.ipv4.neigh.br0/10.mcast_resolicit 0 # tcpdump -nn -e -i br0.10 -Q out arp & # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 # sysctl -wq net.ipv4.neigh.br0/10.mcast_resolicit=3 # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 iproute2 patches can be found here [2]. [1] https://datatracker.ietf.org/doc/html/draft-rbickhart-evpn-ip-mac-proxy-adv-03 [2] https://github.com/idosch/iproute2/tree/submit/extern_valid_v1 Signed-off-by: Ido Schimmel Acked-by: Daniel Borkmann Link: https://patch.msgid.link/20250626073111.244534-2-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt-neigh.yaml | 1 + include/net/neighbour.h | 4 +- include/uapi/linux/neighbour.h | 5 ++ net/core/neighbour.c | 79 +++++++++++++++++++++++++++---- 4 files changed, 78 insertions(+), 11 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/rt-neigh.yaml b/Documentation/netlink/specs/rt-neigh.yaml index 25cc2d528d2f..30a9ee16f128 100644 --- a/Documentation/netlink/specs/rt-neigh.yaml +++ b/Documentation/netlink/specs/rt-neigh.yaml @@ -79,6 +79,7 @@ definitions: entries: - managed - locked + - ext-validated - name: rtm-type type: enum diff --git a/include/net/neighbour.h b/include/net/neighbour.h index c7ce5ec7be23..7e865b14749d 100644 --- a/include/net/neighbour.h +++ b/include/net/neighbour.h @@ -261,13 +261,15 @@ static inline void *neighbour_priv(const struct neighbour *n) #define NEIGH_UPDATE_F_EXT_LEARNED BIT(5) #define NEIGH_UPDATE_F_ISROUTER BIT(6) #define NEIGH_UPDATE_F_ADMIN BIT(7) +#define NEIGH_UPDATE_F_EXT_VALIDATED BIT(8) /* In-kernel representation for NDA_FLAGS_EXT flags: */ #define NTF_OLD_MASK 0xff #define NTF_EXT_SHIFT 8 -#define NTF_EXT_MASK (NTF_EXT_MANAGED) +#define NTF_EXT_MASK (NTF_EXT_MANAGED | NTF_EXT_EXT_VALIDATED) #define NTF_MANAGED (NTF_EXT_MANAGED << NTF_EXT_SHIFT) +#define NTF_EXT_VALIDATED (NTF_EXT_EXT_VALIDATED << NTF_EXT_SHIFT) extern const struct nla_policy nda_policy[]; diff --git a/include/uapi/linux/neighbour.h b/include/uapi/linux/neighbour.h index b851c36ad25d..c34a81245f87 100644 --- a/include/uapi/linux/neighbour.h +++ b/include/uapi/linux/neighbour.h @@ -54,6 +54,7 @@ enum { /* Extended flags under NDA_FLAGS_EXT: */ #define NTF_EXT_MANAGED (1 << 0) #define NTF_EXT_LOCKED (1 << 1) +#define NTF_EXT_EXT_VALIDATED (1 << 2) /* * Neighbor Cache Entry States. @@ -92,6 +93,10 @@ enum { * bridge in response to a host trying to communicate via a locked bridge port * with MAB enabled. Their purpose is to notify user space that a host requires * authentication. + * + * NTF_EXT_EXT_VALIDATED flagged neighbor entries were externally validated by + * a user space control plane. The kernel will not remove or invalidate them, + * but it can probe them and notify user space when they become reachable. */ struct nda_cacheinfo { diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 8ad9898f8e42..e5f0992ac364 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -154,11 +154,12 @@ static void neigh_update_gc_list(struct neighbour *n) if (n->dead) goto out; - /* remove from the gc list if new state is permanent or if neighbor - * is externally learned; otherwise entry should be on the gc list + /* remove from the gc list if new state is permanent or if neighbor is + * externally learned / validated; otherwise entry should be on the gc + * list */ exempt_from_gc = n->nud_state & NUD_PERMANENT || - n->flags & NTF_EXT_LEARNED; + n->flags & (NTF_EXT_LEARNED | NTF_EXT_VALIDATED); on_gc_list = !list_empty(&n->gc_list); if (exempt_from_gc && on_gc_list) { @@ -205,6 +206,7 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, ndm_flags = (flags & NEIGH_UPDATE_F_EXT_LEARNED) ? NTF_EXT_LEARNED : 0; ndm_flags |= (flags & NEIGH_UPDATE_F_MANAGED) ? NTF_MANAGED : 0; + ndm_flags |= (flags & NEIGH_UPDATE_F_EXT_VALIDATED) ? NTF_EXT_VALIDATED : 0; if ((old_flags ^ ndm_flags) & NTF_EXT_LEARNED) { if (ndm_flags & NTF_EXT_LEARNED) @@ -222,6 +224,14 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, *notify = 1; *managed_update = true; } + if ((old_flags ^ ndm_flags) & NTF_EXT_VALIDATED) { + if (ndm_flags & NTF_EXT_VALIDATED) + neigh->flags |= NTF_EXT_VALIDATED; + else + neigh->flags &= ~NTF_EXT_VALIDATED; + *notify = 1; + *gc_update = true; + } } bool neigh_remove_one(struct neighbour *n) @@ -379,7 +389,9 @@ static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev, dev_head = neigh_get_dev_table(dev, tbl->family); hlist_for_each_entry_safe(n, tmp, dev_head, dev_list) { - if (skip_perm && n->nud_state & NUD_PERMANENT) + if (skip_perm && + (n->nud_state & NUD_PERMANENT || + n->flags & NTF_EXT_VALIDATED)) continue; hlist_del_rcu(&n->hash); @@ -942,7 +954,8 @@ static void neigh_periodic_work(struct work_struct *work) state = n->nud_state; if ((state & (NUD_PERMANENT | NUD_IN_TIMER)) || - (n->flags & NTF_EXT_LEARNED)) { + (n->flags & + (NTF_EXT_LEARNED | NTF_EXT_VALIDATED))) { write_unlock(&n->lock); continue; } @@ -1095,9 +1108,15 @@ static void neigh_timer_handler(struct timer_list *t) if ((neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) && atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) { - WRITE_ONCE(neigh->nud_state, NUD_FAILED); + if (neigh->nud_state == NUD_PROBE && + neigh->flags & NTF_EXT_VALIDATED) { + WRITE_ONCE(neigh->nud_state, NUD_STALE); + neigh->updated = jiffies; + } else { + WRITE_ONCE(neigh->nud_state, NUD_FAILED); + neigh_invalidate(neigh); + } notify = 1; - neigh_invalidate(neigh); goto out; } @@ -1245,6 +1264,8 @@ static void neigh_update_hhs(struct neighbour *neigh) NTF_ROUTER flag. NEIGH_UPDATE_F_ISROUTER indicates if the neighbour is known as a router. + NEIGH_UPDATE_F_EXT_VALIDATED means that the entry will not be removed + or invalidated. Caller MUST hold reference count on the entry. */ @@ -1979,7 +2000,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, if (ndm_flags & NTF_PROXY) { struct pneigh_entry *pn; - if (ndm_flags & NTF_MANAGED) { + if (ndm_flags & (NTF_MANAGED | NTF_EXT_VALIDATED)) { NL_SET_ERR_MSG(extack, "Invalid NTF_* flag combination"); goto out; } @@ -2010,7 +2031,8 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, if (neigh == NULL) { bool ndm_permanent = ndm->ndm_state & NUD_PERMANENT; bool exempt_from_gc = ndm_permanent || - ndm_flags & NTF_EXT_LEARNED; + ndm_flags & (NTF_EXT_LEARNED | + NTF_EXT_VALIDATED); if (!(nlh->nlmsg_flags & NLM_F_CREATE)) { err = -ENOENT; @@ -2021,10 +2043,27 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, err = -EINVAL; goto out; } + if (ndm_flags & NTF_EXT_VALIDATED) { + u8 state = ndm->ndm_state; + + /* NTF_USE and NTF_MANAGED will result in the neighbor + * being created with an invalid state (NUD_NONE). + */ + if (ndm_flags & (NTF_USE | NTF_MANAGED)) + state = NUD_NONE; + + if (!(state & NUD_VALID)) { + NL_SET_ERR_MSG(extack, + "Cannot create externally validated neighbor with an invalid state"); + err = -EINVAL; + goto out; + } + } neigh = ___neigh_create(tbl, dst, dev, ndm_flags & - (NTF_EXT_LEARNED | NTF_MANAGED), + (NTF_EXT_LEARNED | NTF_MANAGED | + NTF_EXT_VALIDATED), exempt_from_gc, true); if (IS_ERR(neigh)) { err = PTR_ERR(neigh); @@ -2036,6 +2075,24 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, neigh_release(neigh); goto out; } + if (ndm_flags & NTF_EXT_VALIDATED) { + u8 state = ndm->ndm_state; + + /* NTF_USE and NTF_MANAGED do not update the existing + * state other than clearing it if it was + * NUD_PERMANENT. + */ + if (ndm_flags & (NTF_USE | NTF_MANAGED)) + state = READ_ONCE(neigh->nud_state) & ~NUD_PERMANENT; + + if (!(state & NUD_VALID)) { + NL_SET_ERR_MSG(extack, + "Cannot mark neighbor as externally validated with an invalid state"); + err = -EINVAL; + neigh_release(neigh); + goto out; + } + } if (!(nlh->nlmsg_flags & NLM_F_REPLACE)) flags &= ~(NEIGH_UPDATE_F_OVERRIDE | @@ -2052,6 +2109,8 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, flags |= NEIGH_UPDATE_F_MANAGED; if (ndm_flags & NTF_USE) flags |= NEIGH_UPDATE_F_USE; + if (ndm_flags & NTF_EXT_VALIDATED) + flags |= NEIGH_UPDATE_F_EXT_VALIDATED; err = __neigh_update(neigh, lladdr, ndm->ndm_state, flags, NETLINK_CB(skb).portid, extack); -- cgit v1.2.3 From 566e8f108fc7847f2a8676ec6a101d37b7dd0fb4 Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Sun, 29 Jun 2025 17:21:32 +0300 Subject: devlink: Extend devlink rate API with traffic classes bandwidth management Introduce support for specifying relative bandwidth shares between traffic classes (TC) in the devlink-rate API. This new option allows users to allocate bandwidth across multiple traffic classes in a single command. This feature provides a more granular control over traffic management, especially for scenarios requiring Enhanced Transmission Selection. Users can now define a relative bandwidth share for each traffic class. For example, assigning share values of 20 to TC0 (TCP/UDP) and 80 to TC5 (RoCE) will result in TC0 receiving 20% and TC5 receiving 80% of the total bandwidth. The actual percentage each class receives depends on the ratio of its share value to the sum of all shares. Example: DEV=pci/0000:08:00.0 $ devlink port function rate add $DEV/vfs_group tx_share 10Gbit \ tx_max 50Gbit tc-bw 0:20 1:0 2:0 3:0 4:0 5:80 6:0 7:0 $ devlink port function rate set $DEV/vfs_group \ tc-bw 0:20 1:0 2:0 3:0 4:0 5:20 6:60 7:0 Example usage with ynl: ./tools/net/ynl/cli.py --spec Documentation/netlink/specs/devlink.yaml \ --do rate-set --json '{ "bus-name": "pci", "dev-name": "0000:08:00.0", "port-index": 1, "rate-tc-bws": [ {"rate-tc-index": 0, "rate-tc-bw": 50}, {"rate-tc-index": 1, "rate-tc-bw": 50}, {"rate-tc-index": 2, "rate-tc-bw": 0}, {"rate-tc-index": 3, "rate-tc-bw": 0}, {"rate-tc-index": 4, "rate-tc-bw": 0}, {"rate-tc-index": 5, "rate-tc-bw": 0}, {"rate-tc-index": 6, "rate-tc-bw": 0}, {"rate-tc-index": 7, "rate-tc-bw": 0} ] }' ./tools/net/ynl/cli.py --spec Documentation/netlink/specs/devlink.yaml \ --do rate-get --json '{ "bus-name": "pci", "dev-name": "0000:08:00.0", "port-index": 1 }' output for rate-get: {'bus-name': 'pci', 'dev-name': '0000:08:00.0', 'port-index': 1, 'rate-tc-bws': [{'rate-tc-bw': 50, 'rate-tc-index': 0}, {'rate-tc-bw': 50, 'rate-tc-index': 1}, {'rate-tc-bw': 0, 'rate-tc-index': 2}, {'rate-tc-bw': 0, 'rate-tc-index': 3}, {'rate-tc-bw': 0, 'rate-tc-index': 4}, {'rate-tc-bw': 0, 'rate-tc-index': 5}, {'rate-tc-bw': 0, 'rate-tc-index': 6}, {'rate-tc-bw': 0, 'rate-tc-index': 7}], 'rate-tx-max': 0, 'rate-tx-priority': 0, 'rate-tx-share': 0, 'rate-tx-weight': 0, 'rate-type': 'leaf'} Signed-off-by: Carolina Jubran Reviewed-by: Cosmin Ratiu Reviewed-by: Jiri Pirko Signed-off-by: Tariq Toukan Signed-off-by: Mark Bloch Link: https://patch.msgid.link/20250629142138.361537-3-mbloch@nvidia.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/devlink.yaml | 32 +++++- Documentation/networking/devlink/devlink-port.rst | 8 ++ include/net/devlink.h | 8 ++ include/uapi/linux/devlink.h | 9 ++ net/devlink/netlink_gen.c | 15 ++- net/devlink/netlink_gen.h | 1 + net/devlink/rate.c | 127 ++++++++++++++++++++++ 7 files changed, 195 insertions(+), 5 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index bfba466d694a..1c4bb0cbe5f0 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -224,6 +224,10 @@ definitions: value: 10 - name: binary + - + name: rate-tc-index-max + type: const + value: 7 attribute-sets: - @@ -844,7 +848,23 @@ attribute-sets: - name: region-direct type: flag - + - + name: rate-tc-bws + type: nest + multi-attr: true + nested-attributes: dl-rate-tc-bws + - + name: rate-tc-index + type: u8 + checks: + max: rate-tc-index-max + - + name: rate-tc-bw + type: u32 + doc: | + Specifies the bandwidth share assigned to the Traffic Class. + The bandwidth for the traffic class is determined + in proportion to the sum of the shares of all configured classes. - name: dl-dev-stats subset-of: devlink @@ -1249,6 +1269,14 @@ attribute-sets: - name: flash type: flag + - + name: dl-rate-tc-bws + subset-of: devlink + attributes: + - + name: rate-tc-index + - + name: rate-tc-bw operations: enum-model: directional @@ -2176,6 +2204,7 @@ operations: - rate-tx-priority - rate-tx-weight - rate-parent-node-name + - rate-tc-bws - name: rate-new @@ -2196,6 +2225,7 @@ operations: - rate-tx-priority - rate-tx-weight - rate-parent-node-name + - rate-tc-bws - name: rate-del diff --git a/Documentation/networking/devlink/devlink-port.rst b/Documentation/networking/devlink/devlink-port.rst index 9d22d41a7cd1..5e397798a402 100644 --- a/Documentation/networking/devlink/devlink-port.rst +++ b/Documentation/networking/devlink/devlink-port.rst @@ -418,6 +418,14 @@ API allows to configure following rate object's parameters: to all node children limits. ``tx_max`` is an upper limit for children. ``tx_share`` is a total bandwidth distributed among children. +``tc_bw`` + Allow users to set the bandwidth allocation per traffic class on rate + objects. This enables fine-grained QoS configurations by assigning a relative + share value to each traffic class. The bandwidth is distributed in proportion + to the share value for each class, relative to the sum of all shares. + When applied to a non-leaf node, tc_bw determines how bandwidth is shared + among its child elements. + ``tx_priority`` and ``tx_weight`` can be used simultaneously. In that case nodes with the same priority form a WFQ subgroup in the sibling group and arbitration among them is based on assigned weights. diff --git a/include/net/devlink.h b/include/net/devlink.h index 63517646a497..d0ce5a7e984c 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -118,6 +118,8 @@ struct devlink_rate { u32 tx_priority; u32 tx_weight; + + u32 tc_bw[DEVLINK_RATE_TCS_MAX]; }; struct devlink_port { @@ -1486,6 +1488,9 @@ struct devlink_ops { u32 tx_priority, struct netlink_ext_ack *extack); int (*rate_leaf_tx_weight_set)(struct devlink_rate *devlink_rate, void *priv, u32 tx_weight, struct netlink_ext_ack *extack); + int (*rate_leaf_tc_bw_set)(struct devlink_rate *devlink_rate, + void *priv, u32 *tc_bw, + struct netlink_ext_ack *extack); int (*rate_node_tx_share_set)(struct devlink_rate *devlink_rate, void *priv, u64 tx_share, struct netlink_ext_ack *extack); int (*rate_node_tx_max_set)(struct devlink_rate *devlink_rate, void *priv, @@ -1494,6 +1499,9 @@ struct devlink_ops { u32 tx_priority, struct netlink_ext_ack *extack); int (*rate_node_tx_weight_set)(struct devlink_rate *devlink_rate, void *priv, u32 tx_weight, struct netlink_ext_ack *extack); + int (*rate_node_tc_bw_set)(struct devlink_rate *devlink_rate, + void *priv, u32 *tc_bw, + struct netlink_ext_ack *extack); int (*rate_node_new)(struct devlink_rate *rate_node, void **priv, struct netlink_ext_ack *extack); int (*rate_node_del)(struct devlink_rate *rate_node, void *priv, diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index a5ee0f13740a..e72bcc239afd 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -221,6 +221,11 @@ enum devlink_port_flavour { */ }; +/* IEEE 802.1Qaz standard supported values. */ + +#define DEVLINK_RATE_TCS_MAX 8 +#define DEVLINK_RATE_TC_INDEX_MAX (DEVLINK_RATE_TCS_MAX - 1) + enum devlink_rate_type { DEVLINK_RATE_TYPE_LEAF, DEVLINK_RATE_TYPE_NODE, @@ -629,6 +634,10 @@ enum devlink_attr { DEVLINK_ATTR_REGION_DIRECT, /* flag */ + DEVLINK_ATTR_RATE_TC_BWS, /* nested */ + DEVLINK_ATTR_RATE_TC_INDEX, /* u8 */ + DEVLINK_ATTR_RATE_TC_BW, /* u32 */ + /* Add new attributes above here, update the spec in * Documentation/netlink/specs/devlink.yaml and re-generate * net/devlink/netlink_gen.c. diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c index e340d955cf3b..c50436433c18 100644 --- a/net/devlink/netlink_gen.c +++ b/net/devlink/netlink_gen.c @@ -45,6 +45,11 @@ const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_ [DEVLINK_PORT_FN_ATTR_CAPS] = NLA_POLICY_BITFIELD32(15), }; +const struct nla_policy devlink_dl_rate_tc_bws_nl_policy[DEVLINK_ATTR_RATE_TC_BW + 1] = { + [DEVLINK_ATTR_RATE_TC_INDEX] = NLA_POLICY_MAX(NLA_U8, DEVLINK_RATE_TC_INDEX_MAX), + [DEVLINK_ATTR_RATE_TC_BW] = { .type = NLA_U32, }, +}; + const struct nla_policy devlink_dl_selftest_id_nl_policy[DEVLINK_ATTR_SELFTEST_ID_FLASH + 1] = { [DEVLINK_ATTR_SELFTEST_ID_FLASH] = { .type = NLA_FLAG, }, }; @@ -523,7 +528,7 @@ static const struct nla_policy devlink_rate_get_dump_nl_policy[DEVLINK_ATTR_DEV_ }; /* DEVLINK_CMD_RATE_SET - do */ -static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_RATE_TX_WEIGHT + 1] = { +static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_RATE_TC_BWS + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_RATE_NODE_NAME] = { .type = NLA_NUL_STRING, }, @@ -532,10 +537,11 @@ static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_RATE_TX_W [DEVLINK_ATTR_RATE_TX_PRIORITY] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_TX_WEIGHT] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_RATE_TC_BWS] = NLA_POLICY_NESTED(devlink_dl_rate_tc_bws_nl_policy), }; /* DEVLINK_CMD_RATE_NEW - do */ -static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_RATE_TX_WEIGHT + 1] = { +static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_RATE_TC_BWS + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_RATE_NODE_NAME] = { .type = NLA_NUL_STRING, }, @@ -544,6 +550,7 @@ static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_RATE_TX_W [DEVLINK_ATTR_RATE_TX_PRIORITY] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_TX_WEIGHT] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_RATE_TC_BWS] = NLA_POLICY_NESTED(devlink_dl_rate_tc_bws_nl_policy), }; /* DEVLINK_CMD_RATE_DEL - do */ @@ -1191,7 +1198,7 @@ const struct genl_split_ops devlink_nl_ops[74] = { .doit = devlink_nl_rate_set_doit, .post_doit = devlink_nl_post_doit, .policy = devlink_rate_set_nl_policy, - .maxattr = DEVLINK_ATTR_RATE_TX_WEIGHT, + .maxattr = DEVLINK_ATTR_RATE_TC_BWS, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { @@ -1201,7 +1208,7 @@ const struct genl_split_ops devlink_nl_ops[74] = { .doit = devlink_nl_rate_new_doit, .post_doit = devlink_nl_post_doit, .policy = devlink_rate_new_nl_policy, - .maxattr = DEVLINK_ATTR_RATE_TX_WEIGHT, + .maxattr = DEVLINK_ATTR_RATE_TC_BWS, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/net/devlink/netlink_gen.h b/net/devlink/netlink_gen.h index 8f2bd50ddf5e..fb733b5d4ff1 100644 --- a/net/devlink/netlink_gen.h +++ b/net/devlink/netlink_gen.h @@ -13,6 +13,7 @@ /* Common nested types */ extern const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_CAPS + 1]; +extern const struct nla_policy devlink_dl_rate_tc_bws_nl_policy[DEVLINK_ATTR_RATE_TC_BW + 1]; extern const struct nla_policy devlink_dl_selftest_id_nl_policy[DEVLINK_ATTR_SELFTEST_ID_FLASH + 1]; /* Ops table for devlink */ diff --git a/net/devlink/rate.c b/net/devlink/rate.c index 8828ffaf6cbc..d39300a9b3d4 100644 --- a/net/devlink/rate.c +++ b/net/devlink/rate.c @@ -80,6 +80,29 @@ devlink_rate_get_from_info(struct devlink *devlink, struct genl_info *info) return ERR_PTR(-EINVAL); } +static int devlink_rate_put_tc_bws(struct sk_buff *msg, u32 *tc_bw) +{ + struct nlattr *nla_tc_bw; + int i; + + for (i = 0; i < DEVLINK_RATE_TCS_MAX; i++) { + nla_tc_bw = nla_nest_start(msg, DEVLINK_ATTR_RATE_TC_BWS); + if (!nla_tc_bw) + return -EMSGSIZE; + + if (nla_put_u8(msg, DEVLINK_ATTR_RATE_TC_INDEX, i) || + nla_put_u32(msg, DEVLINK_ATTR_RATE_TC_BW, tc_bw[i])) + goto nla_put_failure; + + nla_nest_end(msg, nla_tc_bw); + } + return 0; + +nla_put_failure: + nla_nest_cancel(msg, nla_tc_bw); + return -EMSGSIZE; +} + static int devlink_nl_rate_fill(struct sk_buff *msg, struct devlink_rate *devlink_rate, enum devlink_command cmd, u32 portid, u32 seq, @@ -129,6 +152,9 @@ static int devlink_nl_rate_fill(struct sk_buff *msg, devlink_rate->parent->name)) goto nla_put_failure; + if (devlink_rate_put_tc_bws(msg, devlink_rate->tc_bw)) + goto nla_put_failure; + genlmsg_end(msg, hdr); return 0; @@ -316,6 +342,87 @@ devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, return 0; } +static int devlink_nl_rate_tc_bw_parse(struct nlattr *parent_nest, u32 *tc_bw, + unsigned long *bitmap, + struct netlink_ext_ack *extack) +{ + struct nlattr *tb[DEVLINK_ATTR_MAX + 1]; + u8 tc_index; + int err; + + err = nla_parse_nested(tb, DEVLINK_ATTR_MAX, parent_nest, + devlink_dl_rate_tc_bws_nl_policy, extack); + if (err) + return err; + + if (!tb[DEVLINK_ATTR_RATE_TC_INDEX]) { + NL_SET_ERR_ATTR_MISS(extack, parent_nest, + DEVLINK_ATTR_RATE_TC_INDEX); + return -EINVAL; + } + + tc_index = nla_get_u8(tb[DEVLINK_ATTR_RATE_TC_INDEX]); + + if (!tb[DEVLINK_ATTR_RATE_TC_BW]) { + NL_SET_ERR_ATTR_MISS(extack, parent_nest, + DEVLINK_ATTR_RATE_TC_BW); + return -EINVAL; + } + + if (test_and_set_bit(tc_index, bitmap)) { + NL_SET_ERR_MSG_FMT(extack, + "Duplicate traffic class index specified (%u)", + tc_index); + return -EINVAL; + } + + tc_bw[tc_index] = nla_get_u32(tb[DEVLINK_ATTR_RATE_TC_BW]); + + return 0; +} + +static int devlink_nl_rate_tc_bw_set(struct devlink_rate *devlink_rate, + struct genl_info *info) +{ + DECLARE_BITMAP(bitmap, DEVLINK_RATE_TCS_MAX) = {}; + struct devlink *devlink = devlink_rate->devlink; + const struct devlink_ops *ops = devlink->ops; + u32 tc_bw[DEVLINK_RATE_TCS_MAX] = {}; + int rem, err = -EOPNOTSUPP, i; + struct nlattr *attr; + + nlmsg_for_each_attr_type(attr, DEVLINK_ATTR_RATE_TC_BWS, info->nlhdr, + GENL_HDRLEN, rem) { + err = devlink_nl_rate_tc_bw_parse(attr, tc_bw, bitmap, + info->extack); + if (err) + return err; + } + + for (i = 0; i < DEVLINK_RATE_TCS_MAX; i++) { + if (!test_bit(i, bitmap)) { + NL_SET_ERR_MSG_FMT(info->extack, + "Bandwidth values must be specified for all %u traffic classes", + DEVLINK_RATE_TCS_MAX); + return -EINVAL; + } + } + + if (devlink_rate_is_leaf(devlink_rate)) + err = ops->rate_leaf_tc_bw_set(devlink_rate, devlink_rate->priv, + tc_bw, info->extack); + else if (devlink_rate_is_node(devlink_rate)) + err = ops->rate_node_tc_bw_set(devlink_rate, devlink_rate->priv, + tc_bw, info->extack); + + if (err) + return err; + + memcpy(devlink_rate->tc_bw, tc_bw, sizeof(tc_bw)); + + return 0; +} + static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, const struct devlink_ops *ops, struct genl_info *info) @@ -388,6 +495,12 @@ static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, return err; } + if (attrs[DEVLINK_ATTR_RATE_TC_BWS]) { + err = devlink_nl_rate_tc_bw_set(devlink_rate, info); + if (err) + return err; + } + return 0; } @@ -423,6 +536,13 @@ static bool devlink_rate_set_ops_supported(const struct devlink_ops *ops, "TX weight set isn't supported for the leafs"); return false; } + if (attrs[DEVLINK_ATTR_RATE_TC_BWS] && + !ops->rate_leaf_tc_bw_set) { + NL_SET_ERR_MSG_ATTR(info->extack, + attrs[DEVLINK_ATTR_RATE_TC_BWS], + "TC bandwidth set isn't supported for the leafs"); + return false; + } } else if (type == DEVLINK_RATE_TYPE_NODE) { if (attrs[DEVLINK_ATTR_RATE_TX_SHARE] && !ops->rate_node_tx_share_set) { NL_SET_ERR_MSG(info->extack, "TX share set isn't supported for the nodes"); @@ -449,6 +569,13 @@ static bool devlink_rate_set_ops_supported(const struct devlink_ops *ops, "TX weight set isn't supported for the nodes"); return false; } + if (attrs[DEVLINK_ATTR_RATE_TC_BWS] && + !ops->rate_node_tc_bw_set) { + NL_SET_ERR_MSG_ATTR(info->extack, + attrs[DEVLINK_ATTR_RATE_TC_BWS], + "TC bandwidth set isn't supported for the nodes"); + return false; + } } else { WARN(1, "Unknown type of rate object"); return false; -- cgit v1.2.3 From 59f44c9ccc3bb68aa3b062b8e57ce0e1ee2fca75 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Wed, 2 Jul 2025 17:50:34 +0200 Subject: net: openvswitch: allow providing upcall pid for the 'execute' command When a packet enters OVS datapath and there is no flow to handle it, packet goes to userspace through a MISS upcall. With per-CPU upcall dispatch mechanism, we're using the current CPU id to select the Netlink PID on which to send this packet. This allows us to send packets from the same traffic flow through the same handler. The handler will process the packet, install required flow into the kernel and re-inject the original packet via OVS_PACKET_CMD_EXECUTE. While handling OVS_PACKET_CMD_EXECUTE, however, we may hit a recirculation action that will pass the (likely modified) packet through the flow lookup again. And if the flow is not found, the packet will be sent to userspace again through another MISS upcall. However, the handler thread in userspace is likely running on a different CPU core, and the OVS_PACKET_CMD_EXECUTE request is handled in the syscall context of that thread. So, when the time comes to send the packet through another upcall, the per-CPU dispatch will choose a different Netlink PID, and this packet will end up processed by a different handler thread on a different CPU. The process continues as long as there are new recirculations, each time the packet goes to a different handler thread before it is sent out of the OVS datapath to the destination port. In real setups the number of recirculations can go up to 4 or 5, sometimes more. There is always a chance to re-order packets while processing upcalls, because userspace will first install the flow and then re-inject the original packet. So, there is a race window when the flow is already installed and the second packet can match it and be forwarded to the destination before the first packet is re-injected. But the fact that packets are going through multiple upcalls handled by different userspace threads makes the reordering noticeably more likely, because we not only have a race between the kernel and a userspace handler (which is hard to avoid), but also between multiple userspace handlers. For example, let's assume that 10 packets got enqueued through a MISS upcall for handler-1, it will start processing them, will install the flow into the kernel and start re-injecting packets back, from where they will go through another MISS to handler-2. Handler-2 will install the flow into the kernel and start re-injecting the packets, while handler-1 continues to re-inject the last of the 10 packets, they will hit the flow installed by handler-2 and be forwarded without going to the handler-2, while handler-2 still re-injects the first of these 10 packets. Given multiple recirculations and misses, these 10 packets may end up completely mixed up on the output from the datapath. Let's allow userspace to specify on which Netlink PID the packets should be upcalled while processing OVS_PACKET_CMD_EXECUTE. This makes it possible to ensure that all the packets are processed by the same handler thread in the userspace even with them being upcalled multiple times in the process. Packets will remain in order since they will be enqueued to the same socket and re-injected in the same order. This doesn't eliminate re-ordering as stated above, since we still have a race between kernel and the userspace thread, but it allows to eliminate races between multiple userspace threads. Userspace knows the PID of the socket on which the original upcall is received, so there is no need to send it up from the kernel. Solution requires storing the value somewhere for the duration of the packet processing. There are two potential places for this: our skb extension or the per-CPU storage. It's not clear which is better, so just following currently used scheme of storing this kind of things along the skb. We still have a decent amount of space in the cb. Signed-off-by: Ilya Maximets Acked-by: Flavio Leitner Acked-by: Eelco Chaudron Acked-by: Aaron Conole Link: https://patch.msgid.link/20250702155043.2331772-1-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- include/uapi/linux/openvswitch.h | 6 ++++++ net/openvswitch/actions.c | 6 ++++-- net/openvswitch/datapath.c | 8 +++++++- net/openvswitch/datapath.h | 3 +++ net/openvswitch/vport.c | 1 + 5 files changed, 21 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/openvswitch.h b/include/uapi/linux/openvswitch.h index 3a701bd1f31b..3092c2c6f1d2 100644 --- a/include/uapi/linux/openvswitch.h +++ b/include/uapi/linux/openvswitch.h @@ -186,6 +186,11 @@ enum ovs_packet_cmd { * %OVS_PACKET_ATTR_USERSPACE action specify the Maximum received fragment * size. * @OVS_PACKET_ATTR_HASH: Packet hash info (e.g. hash, sw_hash and l4_hash in skb). + * @OVS_PACKET_ATTR_UPCALL_PID: Netlink PID to use for upcalls while + * processing %OVS_PACKET_CMD_EXECUTE. Takes precedence over all other ways + * to determine the Netlink PID including %OVS_USERSPACE_ATTR_PID, + * %OVS_DP_ATTR_UPCALL_PID, %OVS_DP_ATTR_PER_CPU_PIDS and the + * %OVS_VPORT_ATTR_UPCALL_PID. * * These attributes follow the &struct ovs_header within the Generic Netlink * payload for %OVS_PACKET_* commands. @@ -205,6 +210,7 @@ enum ovs_packet_attr { OVS_PACKET_ATTR_MRU, /* Maximum received IP fragment size. */ OVS_PACKET_ATTR_LEN, /* Packet size before truncation. */ OVS_PACKET_ATTR_HASH, /* Packet hash. */ + OVS_PACKET_ATTR_UPCALL_PID, /* u32 Netlink PID. */ __OVS_PACKET_ATTR_MAX }; diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index 3add108340bf..2832e0794197 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c @@ -941,8 +941,10 @@ static int output_userspace(struct datapath *dp, struct sk_buff *skb, break; case OVS_USERSPACE_ATTR_PID: - if (dp->user_features & - OVS_DP_F_DISPATCH_UPCALL_PER_CPU) + if (OVS_CB(skb)->upcall_pid) + upcall.portid = OVS_CB(skb)->upcall_pid; + else if (dp->user_features & + OVS_DP_F_DISPATCH_UPCALL_PER_CPU) upcall.portid = ovs_dp_get_upcall_portid(dp, smp_processor_id()); diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index b990dc83504f..d5b6e2002bc1 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -267,7 +267,9 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key) memset(&upcall, 0, sizeof(upcall)); upcall.cmd = OVS_PACKET_CMD_MISS; - if (dp->user_features & OVS_DP_F_DISPATCH_UPCALL_PER_CPU) + if (OVS_CB(skb)->upcall_pid) + upcall.portid = OVS_CB(skb)->upcall_pid; + else if (dp->user_features & OVS_DP_F_DISPATCH_UPCALL_PER_CPU) upcall.portid = ovs_dp_get_upcall_portid(dp, smp_processor_id()); else @@ -651,6 +653,9 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info) !!(hash & OVS_PACKET_HASH_L4_BIT)); } + OVS_CB(packet)->upcall_pid = + nla_get_u32_default(a[OVS_PACKET_ATTR_UPCALL_PID], 0); + /* Build an sw_flow for sending this packet. */ flow = ovs_flow_alloc(); err = PTR_ERR(flow); @@ -719,6 +724,7 @@ static const struct nla_policy packet_policy[OVS_PACKET_ATTR_MAX + 1] = { [OVS_PACKET_ATTR_PROBE] = { .type = NLA_FLAG }, [OVS_PACKET_ATTR_MRU] = { .type = NLA_U16 }, [OVS_PACKET_ATTR_HASH] = { .type = NLA_U64 }, + [OVS_PACKET_ATTR_UPCALL_PID] = { .type = NLA_U32 }, }; static const struct genl_small_ops dp_packet_genl_ops[] = { diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h index cfeb817a1889..db0c3e69d66c 100644 --- a/net/openvswitch/datapath.h +++ b/net/openvswitch/datapath.h @@ -121,6 +121,8 @@ struct datapath { * @cutlen: The number of bytes from the packet end to be removed. * @probability: The sampling probability that was applied to this skb; 0 means * no sampling has occurred; U32_MAX means 100% probability. + * @upcall_pid: Netlink socket PID to use for sending this packet to userspace; + * 0 means "not set" and default per-CPU or per-vport dispatch should be used. */ struct ovs_skb_cb { struct vport *input_vport; @@ -128,6 +130,7 @@ struct ovs_skb_cb { u16 acts_origlen; u32 cutlen; u32 probability; + u32 upcall_pid; }; #define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb) diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c index 8732f6e51ae5..6bbbc16ab778 100644 --- a/net/openvswitch/vport.c +++ b/net/openvswitch/vport.c @@ -501,6 +501,7 @@ int ovs_vport_receive(struct vport *vport, struct sk_buff *skb, OVS_CB(skb)->mru = 0; OVS_CB(skb)->cutlen = 0; OVS_CB(skb)->probability = 0; + OVS_CB(skb)->upcall_pid = 0; if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) { u32 mark; -- cgit v1.2.3 From 3d98ee52659c3f1d3913ae5b97f7743c5247752c Mon Sep 17 00:00:00 2001 From: Tonghao Zhang Date: Fri, 27 Jun 2025 21:49:29 +0800 Subject: net: bonding: add broadcast_neighbor netlink option User can config or display the bonding broadcast_neighbor option via iproute2/netlink. Cc: Jay Vosburgh Cc: "David S. Miller" Cc: Eric Dumazet Cc: Jakub Kicinski Cc: Paolo Abeni Cc: Simon Horman Cc: Jonathan Corbet Cc: Andrew Lunn Cc: Steven Rostedt Cc: Masami Hiramatsu Cc: Mathieu Desnoyers Cc: Nikolay Aleksandrov Signed-off-by: Tonghao Zhang Signed-off-by: Zengbing Tu Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/76b90700ba5b98027dfb51a2f3c5cfea0440a21b.1751031306.git.tonghao@bamaicloud.com Signed-off-by: Paolo Abeni --- drivers/net/bonding/bond_netlink.c | 16 ++++++++++++++++ include/uapi/linux/if_link.h | 1 + 2 files changed, 17 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index ac5e402c34bc..57fff2421f1b 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -124,6 +124,7 @@ static const struct nla_policy bond_policy[IFLA_BOND_MAX + 1] = { [IFLA_BOND_MISSED_MAX] = { .type = NLA_U8 }, [IFLA_BOND_NS_IP6_TARGET] = { .type = NLA_NESTED }, [IFLA_BOND_COUPLED_CONTROL] = { .type = NLA_U8 }, + [IFLA_BOND_BROADCAST_NEIGH] = { .type = NLA_U8 }, }; static const struct nla_policy bond_slave_policy[IFLA_BOND_SLAVE_MAX + 1] = { @@ -561,6 +562,16 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[], return err; } + if (data[IFLA_BOND_BROADCAST_NEIGH]) { + int broadcast_neigh = nla_get_u8(data[IFLA_BOND_BROADCAST_NEIGH]); + + bond_opt_initval(&newval, broadcast_neigh); + err = __bond_opt_set(bond, BOND_OPT_BROADCAST_NEIGH, &newval, + data[IFLA_BOND_BROADCAST_NEIGH], extack); + if (err) + return err; + } + return 0; } @@ -630,6 +641,7 @@ static size_t bond_get_size(const struct net_device *bond_dev) nla_total_size(sizeof(struct nlattr)) + nla_total_size(sizeof(struct in6_addr)) * BOND_MAX_NS_TARGETS + nla_total_size(sizeof(u8)) + /* IFLA_BOND_COUPLED_CONTROL */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_BROADCAST_NEIGH */ 0; } @@ -793,6 +805,10 @@ static int bond_fill_info(struct sk_buff *skb, bond->params.coupled_control)) goto nla_put_failure; + if (nla_put_u8(skb, IFLA_BOND_BROADCAST_NEIGH, + bond->params.broadcast_neighbor)) + goto nla_put_failure; + if (BOND_MODE(bond) == BOND_MODE_8023AD) { struct ad_info info; diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index 873c285996fe..784ace3a519c 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h @@ -1535,6 +1535,7 @@ enum { IFLA_BOND_MISSED_MAX, IFLA_BOND_NS_IP6_TARGET, IFLA_BOND_COUPLED_CONTROL, + IFLA_BOND_BROADCAST_NEIGH, __IFLA_BOND_MAX, }; -- cgit v1.2.3 From ad39c12fcee34b8980a80ad5c803bca9906fbd4e Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Wed, 2 Jul 2025 14:20:13 +0800 Subject: net: mctp: add gateway routing support This change allows for gateway routing, where a route table entry may reference a routable endpoint (by network and EID), instead of routing directly to a netdevice. We add support for a RTM_GATEWAY attribute for netlink route updates, with an attribute format of: struct mctp_fq_addr { unsigned int net; mctp_eid_t eid; } - we need the net here to uniquely identify the target EID, as we no longer have the device reference directly (which would provide the net id in the case of direct routes). This makes route lookups recursive, as a route lookup that returns a gateway route must be resolved into a direct route (ie, to a device) eventually. We provide a limit to the route lookups, to prevent infinite loop routing. The route lookup populates a new 'nexthop' field in the dst structure, which now specifies the key for the neighbour table lookup on device output, rather than using the packet destination address directly. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20250702-dev-forwarding-v5-13-1468191da8a4@codeconstruct.com.au Signed-off-by: Paolo Abeni --- include/net/mctp.h | 13 ++- include/uapi/linux/mctp.h | 8 ++ net/mctp/route.c | 206 +++++++++++++++++++++++++++++++++------------- net/mctp/test/utils.c | 3 +- 4 files changed, 173 insertions(+), 57 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/net/mctp.h b/include/net/mctp.h index b3af0690f607..ac4f4ecdfc24 100644 --- a/include/net/mctp.h +++ b/include/net/mctp.h @@ -237,8 +237,18 @@ struct mctp_route { mctp_eid_t min, max; unsigned char type; + unsigned int mtu; - struct mctp_dev *dev; + + enum { + MCTP_ROUTE_DIRECT, + MCTP_ROUTE_GATEWAY, + } dst_type; + union { + struct mctp_dev *dev; + struct mctp_fq_addr gateway; + }; + int (*output)(struct mctp_dst *dst, struct sk_buff *skb); @@ -256,6 +266,7 @@ struct mctp_route { struct mctp_dst { struct mctp_dev *dev; unsigned int mtu; + mctp_eid_t nexthop; /* set for direct addressing */ unsigned char halen; diff --git a/include/uapi/linux/mctp.h b/include/uapi/linux/mctp.h index e1db65df9359..19ad12a0cd4b 100644 --- a/include/uapi/linux/mctp.h +++ b/include/uapi/linux/mctp.h @@ -37,6 +37,14 @@ struct sockaddr_mctp_ext { __u8 smctp_haddr[MAX_ADDR_LEN]; }; +/* A "fully qualified" MCTP address, which includes the system-local network ID, + * required to uniquely resolve a routable EID. + */ +struct mctp_fq_addr { + unsigned int net; + mctp_eid_t eid; +}; + #define MCTP_NET_ANY 0x0 #define MCTP_ADDR_NULL 0x00 diff --git a/net/mctp/route.c b/net/mctp/route.c index 5eca3ce0ba3c..a20d6b11d418 100644 --- a/net/mctp/route.c +++ b/net/mctp/route.c @@ -563,7 +563,6 @@ out: static int mctp_dst_output(struct mctp_dst *dst, struct sk_buff *skb) { - struct mctp_hdr *hdr = mctp_hdr(skb); char daddr_buf[MAX_ADDR_LEN]; char *daddr = NULL; int rc; @@ -586,7 +585,7 @@ static int mctp_dst_output(struct mctp_dst *dst, struct sk_buff *skb) daddr = dst->haddr; } else { /* If lookup fails let the device handle daddr==NULL */ - if (mctp_neigh_lookup(dst->dev, hdr->dest, daddr_buf) == 0) + if (mctp_neigh_lookup(dst->dev, dst->nexthop, daddr_buf) == 0) daddr = daddr_buf; } @@ -610,7 +609,8 @@ static int mctp_dst_output(struct mctp_dst *dst, struct sk_buff *skb) static void mctp_route_release(struct mctp_route *rt) { if (refcount_dec_and_test(&rt->refs)) { - mctp_dev_put(rt->dev); + if (rt->dst_type == MCTP_ROUTE_DIRECT) + mctp_dev_put(rt->dev); kfree_rcu(rt, rcu); } } @@ -799,10 +799,16 @@ static struct mctp_sk_key *mctp_lookup_prealloc_tag(struct mctp_sock *msk, } /* routing lookups */ +static unsigned int mctp_route_netid(struct mctp_route *rt) +{ + return rt->dst_type == MCTP_ROUTE_DIRECT ? + READ_ONCE(rt->dev->net) : rt->gateway.net; +} + static bool mctp_rt_match_eid(struct mctp_route *rt, unsigned int net, mctp_eid_t eid) { - return READ_ONCE(rt->dev->net) == net && + return mctp_route_netid(rt) == net && rt->min <= eid && rt->max >= eid; } @@ -811,16 +817,21 @@ static bool mctp_rt_compare_exact(struct mctp_route *rt1, struct mctp_route *rt2) { ASSERT_RTNL(); - return rt1->dev->net == rt2->dev->net && + return mctp_route_netid(rt1) == mctp_route_netid(rt2) && rt1->min == rt2->min && rt1->max == rt2->max; } -static void mctp_dst_from_route(struct mctp_dst *dst, struct mctp_route *route) +/* must only be called on a direct route, as the final output hop */ +static void mctp_dst_from_route(struct mctp_dst *dst, mctp_eid_t eid, + unsigned int mtu, struct mctp_route *route) { mctp_dev_hold(route->dev); + dst->nexthop = eid; dst->dev = route->dev; - dst->mtu = route->mtu ?: READ_ONCE(dst->dev->dev->mtu); + dst->mtu = READ_ONCE(dst->dev->dev->mtu); + if (mtu) + dst->mtu = min(dst->mtu, mtu); dst->halen = 0; dst->output = route->output; } @@ -854,6 +865,7 @@ int mctp_dst_from_extaddr(struct mctp_dst *dst, struct net *net, int ifindex, dst->mtu = READ_ONCE(netdev->mtu); dst->halen = halen; dst->output = mctp_dst_output; + dst->nexthop = 0; memcpy(dst->haddr, haddr, halen); rc = 0; @@ -868,24 +880,54 @@ void mctp_dst_release(struct mctp_dst *dst) mctp_dev_put(dst->dev); } +static struct mctp_route *mctp_route_lookup_single(struct net *net, + unsigned int dnet, + mctp_eid_t daddr) +{ + struct mctp_route *rt; + + list_for_each_entry_rcu(rt, &net->mctp.routes, list) { + if (mctp_rt_match_eid(rt, dnet, daddr)) + return rt; + } + + return NULL; +} + /* populates *dst on successful lookup, if set */ int mctp_route_lookup(struct net *net, unsigned int dnet, mctp_eid_t daddr, struct mctp_dst *dst) { + const unsigned int max_depth = 32; + unsigned int depth, mtu = 0; int rc = -EHOSTUNREACH; - struct mctp_route *rt; rcu_read_lock(); - list_for_each_entry_rcu(rt, &net->mctp.routes, list) { - /* TODO: add metrics */ - if (!mctp_rt_match_eid(rt, dnet, daddr)) - continue; + for (depth = 0; depth < max_depth; depth++) { + struct mctp_route *rt; - if (dst) - mctp_dst_from_route(dst, rt); - rc = 0; - break; + rt = mctp_route_lookup_single(net, dnet, daddr); + if (!rt) + break; + + /* clamp mtu to the smallest in the path, allowing 0 + * to specify no restrictions + */ + if (mtu && rt->mtu) + mtu = min(mtu, rt->mtu); + else + mtu = mtu ?: rt->mtu; + + if (rt->dst_type == MCTP_ROUTE_DIRECT) { + if (dst) + mctp_dst_from_route(dst, daddr, mtu, rt); + rc = 0; + break; + + } else if (rt->dst_type == MCTP_ROUTE_GATEWAY) { + daddr = rt->gateway.eid; + } } rcu_read_unlock(); @@ -902,10 +944,13 @@ static int mctp_route_lookup_null(struct net *net, struct net_device *dev, rcu_read_lock(); list_for_each_entry_rcu(rt, &net->mctp.routes, list) { - if (rt->dev->dev != dev || rt->type != RTN_LOCAL) + if (rt->dst_type != MCTP_ROUTE_DIRECT || rt->type != RTN_LOCAL) + continue; + + if (rt->dev->dev != dev) continue; - mctp_dst_from_route(dst, rt); + mctp_dst_from_route(dst, 0, 0, rt); rc = 0; break; } @@ -1085,11 +1130,6 @@ out_release: return rc; } -static unsigned int mctp_route_netid(struct mctp_route *rt) -{ - return rt->dev->net; -} - /* route management */ /* mctp_route_add(): Add the provided route, previously allocated via @@ -1097,9 +1137,9 @@ static unsigned int mctp_route_netid(struct mctp_route *rt) * hold on rt->dev for usage in the route table. On failure a caller will want * to mctp_route_release(). * - * We expect that the caller has set rt->type, rt->min, rt->max, rt->dev and - * rt->mtu, and that the route holds a reference to rt->dev (via mctp_dev_hold). - * Other fields will be populated. + * We expect that the caller has set rt->type, rt->dst_type, rt->min, rt->max, + * rt->mtu and either rt->dev (with a reference held appropriately) or + * rt->gateway. Other fields will be populated. */ static int mctp_route_add(struct net *net, struct mctp_route *rt) { @@ -1108,7 +1148,10 @@ static int mctp_route_add(struct net *net, struct mctp_route *rt) if (!mctp_address_unicast(rt->min) || !mctp_address_unicast(rt->max)) return -EINVAL; - if (!rt->dev) + if (rt->dst_type == MCTP_ROUTE_DIRECT && !rt->dev) + return -EINVAL; + + if (rt->dst_type == MCTP_ROUTE_GATEWAY && !rt->gateway.eid) return -EINVAL; switch (rt->type) { @@ -1177,6 +1220,7 @@ int mctp_route_add_local(struct mctp_dev *mdev, mctp_eid_t addr) rt->min = addr; rt->max = addr; + rt->dst_type = MCTP_ROUTE_DIRECT; rt->dev = mdev; rt->type = RTN_LOCAL; @@ -1203,7 +1247,7 @@ void mctp_route_remove_dev(struct mctp_dev *mdev) ASSERT_RTNL(); list_for_each_entry_safe(rt, tmp, &net->mctp.routes, list) { - if (rt->dev == mdev) { + if (rt->dst_type == MCTP_ROUTE_DIRECT && rt->dev == mdev) { list_del_rcu(&rt->list); /* TODO: immediate RTM_DELROUTE */ mctp_route_release(rt); @@ -1296,21 +1340,28 @@ static const struct nla_policy rta_mctp_policy[RTA_MAX + 1] = { [RTA_DST] = { .type = NLA_U8 }, [RTA_METRICS] = { .type = NLA_NESTED }, [RTA_OIF] = { .type = NLA_U32 }, + [RTA_GATEWAY] = NLA_POLICY_EXACT_LEN(sizeof(struct mctp_fq_addr)), }; static const struct nla_policy rta_metrics_policy[RTAX_MAX + 1] = { [RTAX_MTU] = { .type = NLA_U32 }, }; -/* base parsing; common to both _lookup and _populate variants */ +/* base parsing; common to both _lookup and _populate variants. + * + * For gateway routes (which have a RTA_GATEWAY, and no RTA_OIF), we populate + * *gatweayp. for direct routes (RTA_OIF, no RTA_GATEWAY), we populate *mdev. + */ static int mctp_route_nlparse_common(struct net *net, struct nlmsghdr *nlh, struct netlink_ext_ack *extack, struct nlattr **tb, struct rtmsg **rtm, struct mctp_dev **mdev, + struct mctp_fq_addr *gatewayp, mctp_eid_t *daddr_start) { + struct mctp_fq_addr *gateway = NULL; + unsigned int ifindex = 0; struct net_device *dev; - unsigned int ifindex; int rc; rc = nlmsg_parse(nlh, sizeof(struct rtmsg), tb, RTA_MAX, @@ -1326,11 +1377,44 @@ static int mctp_route_nlparse_common(struct net *net, struct nlmsghdr *nlh, } *daddr_start = nla_get_u8(tb[RTA_DST]); - if (!tb[RTA_OIF]) { - NL_SET_ERR_MSG(extack, "ifindex missing"); + if (tb[RTA_OIF]) + ifindex = nla_get_u32(tb[RTA_OIF]); + + if (tb[RTA_GATEWAY]) + gateway = nla_data(tb[RTA_GATEWAY]); + + if (ifindex && gateway) { + NL_SET_ERR_MSG(extack, + "cannot specify both ifindex and gateway"); + return -EINVAL; + + } else if (ifindex) { + dev = __dev_get_by_index(net, ifindex); + if (!dev) { + NL_SET_ERR_MSG(extack, "bad ifindex"); + return -ENODEV; + } + *mdev = mctp_dev_get_rtnl(dev); + if (!*mdev) + return -ENODEV; + gatewayp->eid = 0; + + } else if (gateway) { + if (!mctp_address_unicast(gateway->eid)) { + NL_SET_ERR_MSG(extack, "bad gateway"); + return -EINVAL; + } + + gatewayp->eid = gateway->eid; + gatewayp->net = gateway->net != MCTP_NET_ANY ? + gateway->net : + READ_ONCE(net->mctp.default_net); + *mdev = NULL; + + } else { + NL_SET_ERR_MSG(extack, "no route output provided"); return -EINVAL; } - ifindex = nla_get_u32(tb[RTA_OIF]); *rtm = nlmsg_data(nlh); if ((*rtm)->rtm_family != AF_MCTP) { @@ -1343,16 +1427,6 @@ static int mctp_route_nlparse_common(struct net *net, struct nlmsghdr *nlh, return -EINVAL; } - dev = __dev_get_by_index(net, ifindex); - if (!dev) { - NL_SET_ERR_MSG(extack, "bad ifindex"); - return -ENODEV; - } - - *mdev = mctp_dev_get_rtnl(dev); - if (!*mdev) - return -ENODEV; - return 0; } @@ -1366,24 +1440,34 @@ static int mctp_route_nlparse_lookup(struct net *net, struct nlmsghdr *nlh, unsigned int *daddr_extent) { struct nlattr *tb[RTA_MAX + 1]; + struct mctp_fq_addr gw; struct mctp_dev *mdev; struct rtmsg *rtm; int rc; rc = mctp_route_nlparse_common(net, nlh, extack, tb, &rtm, - &mdev, daddr_start); + &mdev, &gw, daddr_start); if (rc) return rc; - *netid = mdev->net; + if (mdev) { + *netid = mdev->net; + } else if (gw.eid) { + *netid = gw.net; + } else { + /* bug: _nlparse_common should not allow this */ + return -1; + } + *type = rtm->rtm_type; *daddr_extent = rtm->rtm_dst_len; return 0; } -/* Full route parse for RTM_NEWROUTE: populate @rt. On success, the route will - * hold a reference to the dev. +/* Full route parse for RTM_NEWROUTE: populate @rt. On success, + * MCTP_ROUTE_DIRECT routes (ie, those with a direct dev) will hold a reference + * to that dev. */ static int mctp_route_nlparse_populate(struct net *net, struct nlmsghdr *nlh, struct netlink_ext_ack *extack, @@ -1392,6 +1476,7 @@ static int mctp_route_nlparse_populate(struct net *net, struct nlmsghdr *nlh, struct nlattr *tbx[RTAX_MAX + 1]; struct nlattr *tb[RTA_MAX + 1]; unsigned int daddr_extent; + struct mctp_fq_addr gw; mctp_eid_t daddr_start; struct mctp_dev *dev; struct rtmsg *rtm; @@ -1399,7 +1484,7 @@ static int mctp_route_nlparse_populate(struct net *net, struct nlmsghdr *nlh, int rc; rc = mctp_route_nlparse_common(net, nlh, extack, tb, &rtm, - &dev, &daddr_start); + &dev, &gw, &daddr_start); if (rc) return rc; @@ -1425,8 +1510,15 @@ static int mctp_route_nlparse_populate(struct net *net, struct nlmsghdr *nlh, rt->min = daddr_start; rt->max = daddr_start + daddr_extent; rt->mtu = mtu; - rt->dev = dev; - mctp_dev_hold(rt->dev); + if (gw.eid) { + rt->dst_type = MCTP_ROUTE_GATEWAY; + rt->gateway.eid = gw.eid; + rt->gateway.net = gw.net; + } else { + rt->dst_type = MCTP_ROUTE_DIRECT; + rt->dev = dev; + mctp_dev_hold(rt->dev); + } return 0; } @@ -1446,7 +1538,8 @@ static int mctp_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, if (rc < 0) goto err_free; - if (rt->dev->dev->flags & IFF_LOOPBACK) { + if (rt->dst_type == MCTP_ROUTE_DIRECT && + rt->dev->dev->flags & IFF_LOOPBACK) { NL_SET_ERR_MSG(extack, "no routes to loopback"); rc = -EINVAL; goto err_free; @@ -1505,7 +1598,6 @@ static int mctp_fill_rtinfo(struct sk_buff *skb, struct mctp_route *rt, hdr->rtm_tos = 0; hdr->rtm_table = RT_TABLE_DEFAULT; hdr->rtm_protocol = RTPROT_STATIC; /* everything is user-defined */ - hdr->rtm_scope = RT_SCOPE_LINK; /* TODO: scope in mctp_route? */ hdr->rtm_type = rt->type; if (nla_put_u8(skb, RTA_DST, rt->min)) @@ -1522,13 +1614,17 @@ static int mctp_fill_rtinfo(struct sk_buff *skb, struct mctp_route *rt, nla_nest_end(skb, metrics); - if (rt->dev) { + if (rt->dst_type == MCTP_ROUTE_DIRECT) { + hdr->rtm_scope = RT_SCOPE_LINK; if (nla_put_u32(skb, RTA_OIF, rt->dev->dev->ifindex)) goto cancel; + } else if (rt->dst_type == MCTP_ROUTE_GATEWAY) { + hdr->rtm_scope = RT_SCOPE_UNIVERSE; + if (nla_put(skb, RTA_GATEWAY, + sizeof(rt->gateway), &rt->gateway)) + goto cancel; } - /* TODO: conditional neighbour physaddr? */ - nlmsg_end(skb, nlh); return 0; diff --git a/net/mctp/test/utils.c b/net/mctp/test/utils.c index 6b4dc40d882c..97b05e340586 100644 --- a/net/mctp/test/utils.c +++ b/net/mctp/test/utils.c @@ -134,6 +134,7 @@ struct mctp_test_route *mctp_test_create_route(struct net *net, rt->rt.max = eid; rt->rt.mtu = mtu; rt->rt.type = RTN_UNSPEC; + rt->rt.dst_type = MCTP_ROUTE_DIRECT; if (dev) mctp_dev_hold(dev); rt->rt.dev = dev; @@ -176,7 +177,7 @@ void mctp_test_route_destroy(struct kunit *test, struct mctp_test_route *rt) list_del_rcu(&rt->rt.list); rtnl_unlock(); - if (rt->rt.dev) + if (rt->rt.dst_type == MCTP_ROUTE_DIRECT && rt->rt.dev) mctp_dev_put(rt->rt.dev); refs = refcount_read(&rt->rt.refs); -- cgit v1.2.3 From e22da4685013922ade8e7b5e727ac6804fe5b51e Mon Sep 17 00:00:00 2001 From: Hannes Reinecke Date: Tue, 1 Jul 2025 16:46:57 +0200 Subject: net/handshake: Add new parameter 'HANDSHAKE_A_ACCEPT_KEYRING' Add a new netlink parameter 'HANDSHAKE_A_ACCEPT_KEYRING' to provide the serial number of the keyring to use. Signed-off-by: Hannes Reinecke Reviewed-by: Chuck Lever Acked-by: Jakub Kicinski Link: https://patch.msgid.link/20250701144657.104401-1-hare@kernel.org Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/handshake.yaml | 4 ++++ include/uapi/linux/handshake.h | 1 + net/handshake/tlshd.c | 6 ++++++ 3 files changed, 11 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/handshake.yaml b/Documentation/netlink/specs/handshake.yaml index 39ed1661c7f1..95c3fade7a8d 100644 --- a/Documentation/netlink/specs/handshake.yaml +++ b/Documentation/netlink/specs/handshake.yaml @@ -71,6 +71,9 @@ attribute-sets: - name: peername type: string + - + name: keyring + type: u32 - name: done attributes: @@ -109,6 +112,7 @@ operations: - peer-identity - certificate - peername + - keyring - name: done doc: Handler reports handshake completion diff --git a/include/uapi/linux/handshake.h b/include/uapi/linux/handshake.h index 3d7ea58778c9..662e7de46c54 100644 --- a/include/uapi/linux/handshake.h +++ b/include/uapi/linux/handshake.h @@ -45,6 +45,7 @@ enum { HANDSHAKE_A_ACCEPT_PEER_IDENTITY, HANDSHAKE_A_ACCEPT_CERTIFICATE, HANDSHAKE_A_ACCEPT_PEERNAME, + HANDSHAKE_A_ACCEPT_KEYRING, __HANDSHAKE_A_ACCEPT_MAX, HANDSHAKE_A_ACCEPT_MAX = (__HANDSHAKE_A_ACCEPT_MAX - 1) diff --git a/net/handshake/tlshd.c b/net/handshake/tlshd.c index d6f52839827e..081093dfd553 100644 --- a/net/handshake/tlshd.c +++ b/net/handshake/tlshd.c @@ -230,6 +230,12 @@ static int tls_handshake_accept(struct handshake_req *req, if (ret < 0) goto out_cancel; } + if (treq->th_keyring) { + ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_KEYRING, + treq->th_keyring); + if (ret < 0) + goto out_cancel; + } ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_AUTH_MODE, treq->th_auth_mode); -- cgit v1.2.3 From 333c515d189657c934470c9b0b8a8fedb016ce6f Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Jul 2025 17:54:54 +0200 Subject: vhost-net: allow configuring extended features Use the extended feature type for 'acked_features' and implement two new ioctls operation allowing the user-space to set/query an unbounded amount of features. The actual number of processed features is limited by VIRTIO_FEATURES_MAX and attempts to set features above such limit fail with EOPNOTSUPP. Note that: the legacy ioctls implicitly truncate the negotiated features to the lower 64 bits range and the 'acked_backend_features' field don't need conversion, as the only negotiated feature there is in the low 64 bit range. Acked-by: Jason Wang Signed-off-by: Paolo Abeni --- drivers/vhost/net.c | 87 +++++++++++++++++++++++++++++++--------- drivers/vhost/vhost.c | 2 +- drivers/vhost/vhost.h | 4 +- include/uapi/linux/vhost.h | 7 ++++ include/uapi/linux/vhost_types.h | 5 +++ 5 files changed, 82 insertions(+), 23 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 7cbfc7d718b3..67d011b0d4f7 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -69,12 +69,12 @@ MODULE_PARM_DESC(experimental_zcopytx, "Enable Zero Copy TX;" #define VHOST_DMA_IS_DONE(len) ((__force u32)(len) >= (__force u32)VHOST_DMA_DONE_LEN) -enum { - VHOST_NET_FEATURES = VHOST_FEATURES | - (1ULL << VHOST_NET_F_VIRTIO_NET_HDR) | - (1ULL << VIRTIO_NET_F_MRG_RXBUF) | - (1ULL << VIRTIO_F_ACCESS_PLATFORM) | - (1ULL << VIRTIO_F_RING_RESET) +static const u64 vhost_net_features[VIRTIO_FEATURES_DWORDS] = { + VHOST_FEATURES | + (1ULL << VHOST_NET_F_VIRTIO_NET_HDR) | + (1ULL << VIRTIO_NET_F_MRG_RXBUF) | + (1ULL << VIRTIO_F_ACCESS_PLATFORM) | + (1ULL << VIRTIO_F_RING_RESET), }; enum { @@ -1614,16 +1614,17 @@ done: return err; } -static int vhost_net_set_features(struct vhost_net *n, u64 features) +static int vhost_net_set_features(struct vhost_net *n, const u64 *features) { size_t vhost_hlen, sock_hlen, hdr_len; int i; - hdr_len = (features & ((1ULL << VIRTIO_NET_F_MRG_RXBUF) | - (1ULL << VIRTIO_F_VERSION_1))) ? - sizeof(struct virtio_net_hdr_mrg_rxbuf) : - sizeof(struct virtio_net_hdr); - if (features & (1 << VHOST_NET_F_VIRTIO_NET_HDR)) { + hdr_len = virtio_features_test_bit(features, VIRTIO_NET_F_MRG_RXBUF) || + virtio_features_test_bit(features, VIRTIO_F_VERSION_1) ? + sizeof(struct virtio_net_hdr_mrg_rxbuf) : + sizeof(struct virtio_net_hdr); + + if (virtio_features_test_bit(features, VHOST_NET_F_VIRTIO_NET_HDR)) { /* vhost provides vnet_hdr */ vhost_hlen = hdr_len; sock_hlen = 0; @@ -1633,18 +1634,19 @@ static int vhost_net_set_features(struct vhost_net *n, u64 features) sock_hlen = hdr_len; } mutex_lock(&n->dev.mutex); - if ((features & (1 << VHOST_F_LOG_ALL)) && + if (virtio_features_test_bit(features, VHOST_F_LOG_ALL) && !vhost_log_access_ok(&n->dev)) goto out_unlock; - if ((features & (1ULL << VIRTIO_F_ACCESS_PLATFORM))) { + if (virtio_features_test_bit(features, VIRTIO_F_ACCESS_PLATFORM)) { if (vhost_init_device_iotlb(&n->dev)) goto out_unlock; } for (i = 0; i < VHOST_NET_VQ_MAX; ++i) { mutex_lock(&n->vqs[i].vq.mutex); - n->vqs[i].vq.acked_features = features; + virtio_features_copy(n->vqs[i].vq.acked_features_array, + features); n->vqs[i].vhost_hlen = vhost_hlen; n->vqs[i].sock_hlen = sock_hlen; mutex_unlock(&n->vqs[i].vq.mutex); @@ -1681,12 +1683,13 @@ out: static long vhost_net_ioctl(struct file *f, unsigned int ioctl, unsigned long arg) { + u64 all_features[VIRTIO_FEATURES_DWORDS]; struct vhost_net *n = f->private_data; void __user *argp = (void __user *)arg; u64 __user *featurep = argp; struct vhost_vring_file backend; - u64 features; - int r; + u64 features, count, copied; + int r, i; switch (ioctl) { case VHOST_NET_SET_BACKEND: @@ -1694,16 +1697,60 @@ static long vhost_net_ioctl(struct file *f, unsigned int ioctl, return -EFAULT; return vhost_net_set_backend(n, backend.index, backend.fd); case VHOST_GET_FEATURES: - features = VHOST_NET_FEATURES; + features = vhost_net_features[0]; if (copy_to_user(featurep, &features, sizeof features)) return -EFAULT; return 0; case VHOST_SET_FEATURES: if (copy_from_user(&features, featurep, sizeof features)) return -EFAULT; - if (features & ~VHOST_NET_FEATURES) + if (features & ~vhost_net_features[0]) return -EOPNOTSUPP; - return vhost_net_set_features(n, features); + + virtio_features_from_u64(all_features, features); + return vhost_net_set_features(n, all_features); + case VHOST_GET_FEATURES_ARRAY: + if (copy_from_user(&count, featurep, sizeof(count))) + return -EFAULT; + + /* Copy the net features, up to the user-provided buffer size */ + argp += sizeof(u64); + copied = min(count, VIRTIO_FEATURES_DWORDS); + if (copy_to_user(argp, vhost_net_features, + copied * sizeof(u64))) + return -EFAULT; + + /* Zero the trailing space provided by user-space, if any */ + if (clear_user(argp, size_mul(count - copied, sizeof(u64)))) + return -EFAULT; + return 0; + case VHOST_SET_FEATURES_ARRAY: + if (copy_from_user(&count, featurep, sizeof(count))) + return -EFAULT; + + virtio_features_zero(all_features); + argp += sizeof(u64); + copied = min(count, VIRTIO_FEATURES_DWORDS); + if (copy_from_user(all_features, argp, copied * sizeof(u64))) + return -EFAULT; + + /* + * Any feature specified by user-space above + * VIRTIO_FEATURES_MAX is not supported by definition. + */ + for (i = copied; i < count; ++i) { + if (copy_from_user(&features, featurep + 1 + i, + sizeof(features))) + return -EFAULT; + if (features) + return -EOPNOTSUPP; + } + + for (i = 0; i < VIRTIO_FEATURES_DWORDS; i++) + if (all_features[i] & ~vhost_net_features[i]) + return -EOPNOTSUPP; + + return vhost_net_set_features(n, all_features); case VHOST_GET_BACKEND_FEATURES: features = VHOST_NET_BACKEND_FEATURES; if (copy_to_user(featurep, &features, sizeof(features))) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 3a5ebb973dba..1094256a943c 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -372,7 +372,7 @@ static void vhost_vq_reset(struct vhost_dev *dev, vq->log_used = false; vq->log_addr = -1ull; vq->private_data = NULL; - vq->acked_features = 0; + virtio_features_zero(vq->acked_features_array); vq->acked_backend_features = 0; vq->log_base = NULL; vq->error_ctx = NULL; diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index bb75a292d50c..d1aed35c4b07 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -133,7 +133,7 @@ struct vhost_virtqueue { struct vhost_iotlb *umem; struct vhost_iotlb *iotlb; void *private_data; - u64 acked_features; + VIRTIO_DECLARE_FEATURES(acked_features); u64 acked_backend_features; /* Log write descriptors */ void __user *log_base; @@ -291,7 +291,7 @@ static inline void *vhost_vq_get_backend(struct vhost_virtqueue *vq) static inline bool vhost_has_feature(struct vhost_virtqueue *vq, int bit) { - return vq->acked_features & (1ULL << bit); + return virtio_features_test_bit(vq->acked_features_array, bit); } static inline bool vhost_backend_has_feature(struct vhost_virtqueue *vq, int bit) diff --git a/include/uapi/linux/vhost.h b/include/uapi/linux/vhost.h index d4b3e2ae1314..d6ad01fbb8d2 100644 --- a/include/uapi/linux/vhost.h +++ b/include/uapi/linux/vhost.h @@ -235,4 +235,11 @@ */ #define VHOST_VDPA_GET_VRING_SIZE _IOWR(VHOST_VIRTIO, 0x82, \ struct vhost_vring_state) + +/* Extended features manipulation */ +#define VHOST_GET_FEATURES_ARRAY _IOR(VHOST_VIRTIO, 0x83, \ + struct vhost_features_array) +#define VHOST_SET_FEATURES_ARRAY _IOW(VHOST_VIRTIO, 0x83, \ + struct vhost_features_array) + #endif diff --git a/include/uapi/linux/vhost_types.h b/include/uapi/linux/vhost_types.h index d7656908f730..1c39cc5f5a31 100644 --- a/include/uapi/linux/vhost_types.h +++ b/include/uapi/linux/vhost_types.h @@ -110,6 +110,11 @@ struct vhost_msg_v2 { }; }; +struct vhost_features_array { + __u64 count; /* number of entries present in features array */ + __u64 features[] __counted_by(count); +}; + struct vhost_memory_region { __u64 guest_phys_addr; __u64 memory_size; /* bytes */ -- cgit v1.2.3 From a2fb4bc4e2a6a031683910d85b278c1d25ae5420 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Jul 2025 17:54:59 +0200 Subject: net: implement virtio helpers to handle UDP GSO tunneling. The virtio specification are introducing support for GSO over UDP tunnel. This patch brings in the needed defines and the additional virtio hdr parsing/building helpers. The UDP tunnel support uses additional fields in the virtio hdr, and such fields location can change depending on other negotiated features - specifically VIRTIO_NET_F_HASH_REPORT. Try to be as conservative as possible with the new field validation. Existing implementation for plain GSO offloads allow for invalid/ self-contradictory values of such fields. With GSO over UDP tunnel we can be more strict, with no need to deal with legacy implementation. Since the checksum-related field validation is asymmetric in the driver and in the device, introduce a separate helper to implement the new checks (to be used only on the driver side). Note that while the feature space exceeds the 64-bit boundaries, the guest offload space is fixed by the specification of the VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET command to a 64-bit size. Prior to the UDP tunnel GSO support, each guest offload bit corresponded to the feature bit with the same value and vice versa. Due to the limited 'guest offload' space, relevant features in the high 64 bits are 'mapped' to free bits in the lower range. That is simpler than defining a new command (and associated features) to exchange an extended guest offloads set. As a consequence, the uAPIs also specify the mapped guest offload value corresponding to the UDP tunnel GSO features. Acked-by: Jason Wang Signed-off-by: Paolo Abeni -- v4 -> v5: - avoid lines above 80 chars v3 -> v4: - fixed offset for UDP GSO tunnel, update accordingly the helpers - tried to clarified vlan_hlen semantic - virtio_net_chk_data_valid() -> virtio_net_handle_csum_offload() v2 -> v3: - add definitions for possible vnet hdr layouts with tunnel support v1 -> v2: - 'relay' -> 'rely' typo - less unclear comment WRT enforced inner GSO checks - inner header fields are allowed only with 'modern' virtio, thus are always le - clarified in the commit message the need for 'mapped features' defines - assume little_endian is true when UDP GSO is enabled. - fix inner proto type value --- include/linux/virtio_net.h | 197 ++++++++++++++++++++++++++++++++++++++-- include/uapi/linux/virtio_net.h | 33 +++++++ 2 files changed, 222 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index 02a9f4dc594d..20e0584db1dd 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -47,9 +47,9 @@ static inline int virtio_net_hdr_set_proto(struct sk_buff *skb, return 0; } -static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, - const struct virtio_net_hdr *hdr, - bool little_endian) +static inline int __virtio_net_hdr_to_skb(struct sk_buff *skb, + const struct virtio_net_hdr *hdr, + bool little_endian, u8 hdr_gso_type) { unsigned int nh_min_len = sizeof(struct iphdr); unsigned int gso_type = 0; @@ -57,8 +57,8 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, unsigned int p_off = 0; unsigned int ip_proto; - if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) { - switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) { + if (hdr_gso_type != VIRTIO_NET_HDR_GSO_NONE) { + switch (hdr_gso_type & ~VIRTIO_NET_HDR_GSO_ECN) { case VIRTIO_NET_HDR_GSO_TCPV4: gso_type = SKB_GSO_TCPV4; ip_proto = IPPROTO_TCP; @@ -84,7 +84,7 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, return -EINVAL; } - if (hdr->gso_type & VIRTIO_NET_HDR_GSO_ECN) + if (hdr_gso_type & VIRTIO_NET_HDR_GSO_ECN) gso_type |= SKB_GSO_TCP_ECN; if (hdr->gso_size == 0) @@ -122,7 +122,8 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, if (!protocol) virtio_net_hdr_set_proto(skb, hdr); - else if (!virtio_net_hdr_match_proto(protocol, hdr->gso_type)) + else if (!virtio_net_hdr_match_proto(protocol, + hdr_gso_type)) return -EINVAL; else skb->protocol = protocol; @@ -153,7 +154,7 @@ retry: } } - if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) { + if (hdr_gso_type != VIRTIO_NET_HDR_GSO_NONE) { u16 gso_size = __virtio16_to_cpu(little_endian, hdr->gso_size); unsigned int nh_off = p_off; struct skb_shared_info *shinfo = skb_shinfo(skb); @@ -199,6 +200,13 @@ retry: return 0; } +static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, + const struct virtio_net_hdr *hdr, + bool little_endian) +{ + return __virtio_net_hdr_to_skb(skb, hdr, little_endian, hdr->gso_type); +} + static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, struct virtio_net_hdr *hdr, bool little_endian, @@ -242,4 +250,177 @@ static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, return 0; } +static inline unsigned int virtio_l3min(bool is_ipv6) +{ + return is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr); +} + +static inline int +virtio_net_hdr_tnl_to_skb(struct sk_buff *skb, + const struct virtio_net_hdr_v1_hash_tunnel *vhdr, + bool tnl_hdr_negotiated, + bool tnl_csum_negotiated, + bool little_endian) +{ + const struct virtio_net_hdr *hdr = (const struct virtio_net_hdr *)vhdr; + unsigned int inner_nh, outer_th, inner_th; + unsigned int inner_l3min, outer_l3min; + u8 gso_inner_type, gso_tunnel_type; + bool outer_isv6, inner_isv6; + int ret; + + gso_tunnel_type = hdr->gso_type & VIRTIO_NET_HDR_GSO_UDP_TUNNEL; + if (!gso_tunnel_type) + return virtio_net_hdr_to_skb(skb, hdr, little_endian); + + /* Tunnel not supported/negotiated, but the hdr asks for it. */ + if (!tnl_hdr_negotiated) + return -EINVAL; + + /* Either ipv4 or ipv6. */ + if (gso_tunnel_type == VIRTIO_NET_HDR_GSO_UDP_TUNNEL) + return -EINVAL; + + /* The UDP tunnel must carry a GSO packet, but no UFO. */ + gso_inner_type = hdr->gso_type & ~(VIRTIO_NET_HDR_GSO_ECN | + VIRTIO_NET_HDR_GSO_UDP_TUNNEL); + if (!gso_inner_type || gso_inner_type == VIRTIO_NET_HDR_GSO_UDP) + return -EINVAL; + + /* Rely on csum being present. */ + if (!(hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)) + return -EINVAL; + + /* Validate offsets. */ + outer_isv6 = gso_tunnel_type & VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV6; + inner_isv6 = gso_inner_type == VIRTIO_NET_HDR_GSO_TCPV6; + inner_l3min = virtio_l3min(inner_isv6); + outer_l3min = ETH_HLEN + virtio_l3min(outer_isv6); + + inner_th = __virtio16_to_cpu(little_endian, hdr->csum_start); + inner_nh = le16_to_cpu(vhdr->inner_nh_offset); + outer_th = le16_to_cpu(vhdr->outer_th_offset); + if (outer_th < outer_l3min || + inner_nh < outer_th + sizeof(struct udphdr) || + inner_th < inner_nh + inner_l3min) + return -EINVAL; + + /* Let the basic parsing deal with plain GSO features. */ + ret = __virtio_net_hdr_to_skb(skb, hdr, true, + hdr->gso_type & ~gso_tunnel_type); + if (ret) + return ret; + + /* In case of USO, the inner protocol is still unknown and + * `inner_isv6` is just a guess, additional parsing is needed. + * The previous validation ensures that accessing an ipv4 inner + * network header is safe. + */ + if (gso_inner_type == VIRTIO_NET_HDR_GSO_UDP_L4) { + struct iphdr *iphdr = (struct iphdr *)(skb->data + inner_nh); + + inner_isv6 = iphdr->version == 6; + inner_l3min = virtio_l3min(inner_isv6); + if (inner_th < inner_nh + inner_l3min) + return -EINVAL; + } + + skb_set_inner_protocol(skb, inner_isv6 ? htons(ETH_P_IPV6) : + htons(ETH_P_IP)); + if (hdr->flags & VIRTIO_NET_HDR_F_UDP_TUNNEL_CSUM) { + if (!tnl_csum_negotiated) + return -EINVAL; + + skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_TUNNEL_CSUM; + } else { + skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_TUNNEL; + } + + skb->inner_transport_header = inner_th + skb_headroom(skb); + skb->inner_network_header = inner_nh + skb_headroom(skb); + skb->inner_mac_header = inner_nh + skb_headroom(skb); + skb->transport_header = outer_th + skb_headroom(skb); + skb->encapsulation = 1; + return 0; +} + +/* Checksum-related fields validation for the driver */ +static inline int virtio_net_handle_csum_offload(struct sk_buff *skb, + struct virtio_net_hdr *hdr, + bool tnl_csum_negotiated) +{ + if (!(hdr->gso_type & VIRTIO_NET_HDR_GSO_UDP_TUNNEL)) { + if (!(hdr->flags & VIRTIO_NET_HDR_F_DATA_VALID)) + return 0; + + skb->ip_summed = CHECKSUM_UNNECESSARY; + if (!(hdr->flags & VIRTIO_NET_HDR_F_UDP_TUNNEL_CSUM)) + return 0; + + /* tunnel csum packets are invalid when the related + * feature has not been negotiated + */ + if (!tnl_csum_negotiated) + return -EINVAL; + skb->csum_level = 1; + return 0; + } + + /* DATA_VALID is mutually exclusive with NEEDS_CSUM, and GSO + * over UDP tunnel requires the latter + */ + if (hdr->flags & VIRTIO_NET_HDR_F_DATA_VALID) + return -EINVAL; + return 0; +} + +/* + * vlan_hlen always refers to the outermost MAC header. That also + * means it refers to the only MAC header, if the packet does not carry + * any encapsulation. + */ +static inline int +virtio_net_hdr_tnl_from_skb(const struct sk_buff *skb, + struct virtio_net_hdr_v1_hash_tunnel *vhdr, + bool tnl_hdr_negotiated, + bool little_endian, + int vlan_hlen) +{ + struct virtio_net_hdr *hdr = (struct virtio_net_hdr *)vhdr; + unsigned int inner_nh, outer_th; + int tnl_gso_type; + int ret; + + tnl_gso_type = skb_shinfo(skb)->gso_type & (SKB_GSO_UDP_TUNNEL | + SKB_GSO_UDP_TUNNEL_CSUM); + if (!tnl_gso_type) + return virtio_net_hdr_from_skb(skb, hdr, little_endian, false, + vlan_hlen); + + /* Tunnel support not negotiated but skb ask for it. */ + if (!tnl_hdr_negotiated) + return -EINVAL; + + /* Let the basic parsing deal with plain GSO features. */ + skb_shinfo(skb)->gso_type &= ~tnl_gso_type; + ret = virtio_net_hdr_from_skb(skb, hdr, true, false, vlan_hlen); + skb_shinfo(skb)->gso_type |= tnl_gso_type; + if (ret) + return ret; + + if (skb->protocol == htons(ETH_P_IPV6)) + hdr->gso_type |= VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV6; + else + hdr->gso_type |= VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV4; + + if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL_CSUM) + hdr->flags |= VIRTIO_NET_HDR_F_UDP_TUNNEL_CSUM; + + inner_nh = skb->inner_network_header - skb_headroom(skb); + outer_th = skb->transport_header - skb_headroom(skb); + vhdr->inner_nh_offset = cpu_to_le16(inner_nh); + vhdr->outer_th_offset = cpu_to_le16(outer_th); + return 0; +} + #endif /* _LINUX_VIRTIO_NET_H */ diff --git a/include/uapi/linux/virtio_net.h b/include/uapi/linux/virtio_net.h index 963540deae66..8bf27ab8bcb4 100644 --- a/include/uapi/linux/virtio_net.h +++ b/include/uapi/linux/virtio_net.h @@ -70,6 +70,28 @@ * with the same MAC. */ #define VIRTIO_NET_F_SPEED_DUPLEX 63 /* Device set linkspeed and duplex */ +#define VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO 65 /* Driver can receive + * GSO-over-UDP-tunnel packets + */ +#define VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO_CSUM 66 /* Driver handles + * GSO-over-UDP-tunnel + * packets with partial csum + * for the outer header + */ +#define VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO 67 /* Device can receive + * GSO-over-UDP-tunnel packets + */ +#define VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO_CSUM 68 /* Device handles + * GSO-over-UDP-tunnel + * packets with partial csum + * for the outer header + */ + +/* Offloads bits corresponding to VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO{,_CSUM} + * features + */ +#define VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO_MAPPED 46 +#define VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO_CSUM_MAPPED 47 #ifndef VIRTIO_NET_NO_LEGACY #define VIRTIO_NET_F_GSO 6 /* Host handles pkts w/ any GSO type */ @@ -131,12 +153,17 @@ struct virtio_net_hdr_v1 { #define VIRTIO_NET_HDR_F_NEEDS_CSUM 1 /* Use csum_start, csum_offset */ #define VIRTIO_NET_HDR_F_DATA_VALID 2 /* Csum is valid */ #define VIRTIO_NET_HDR_F_RSC_INFO 4 /* rsc info in csum_ fields */ +#define VIRTIO_NET_HDR_F_UDP_TUNNEL_CSUM 8 /* UDP tunnel csum offload */ __u8 flags; #define VIRTIO_NET_HDR_GSO_NONE 0 /* Not a GSO frame */ #define VIRTIO_NET_HDR_GSO_TCPV4 1 /* GSO frame, IPv4 TCP (TSO) */ #define VIRTIO_NET_HDR_GSO_UDP 3 /* GSO frame, IPv4 UDP (UFO) */ #define VIRTIO_NET_HDR_GSO_TCPV6 4 /* GSO frame, IPv6 TCP */ #define VIRTIO_NET_HDR_GSO_UDP_L4 5 /* GSO frame, IPv4& IPv6 UDP (USO) */ +#define VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV4 0x20 /* UDPv4 tunnel present */ +#define VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV6 0x40 /* UDPv6 tunnel present */ +#define VIRTIO_NET_HDR_GSO_UDP_TUNNEL (VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV4 | \ + VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV6) #define VIRTIO_NET_HDR_GSO_ECN 0x80 /* TCP has ECN set */ __u8 gso_type; __virtio16 hdr_len; /* Ethernet + IP + tcp/udp hdrs */ @@ -181,6 +208,12 @@ struct virtio_net_hdr_v1_hash { __le16 padding; }; +struct virtio_net_hdr_v1_hash_tunnel { + struct virtio_net_hdr_v1_hash hash_hdr; + __le16 outer_th_offset; + __le16 inner_nh_offset; +}; + #ifndef VIRTIO_NET_NO_LEGACY /* This header comes first in the scatter-gather list. * For legacy virtio, if VIRTIO_F_ANY_LAYOUT is not negotiated, it must -- cgit v1.2.3 From 288f30435132d2f9e7a29ec9b9745a4f9dc7fd37 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Jul 2025 17:55:30 +0200 Subject: tun: enable gso over UDP tunnel support. Add new tun features to represent the newly introduced virtio GSO over UDP tunnel offload. Allows detection and selection of such features via the existing TUNSETOFFLOAD ioctl and compute the expected virtio header size and tunnel header offset using the current netdev features, so that we can plug almost seamless the newly introduced virtio helpers to serialize the extended virtio header. Acked-by: Jason Wang Signed-off-by: Paolo Abeni --- v6 -> v7: - rebased v4 -> v5: - encapsulate the guest feature guessing in a tun helper - dropped irrelevant check on xdp buff headroom - do not remove unrelated black line - avoid line len > 80 char v3 -> v4: - virtio tnl-related fields are at fixed offset, cleanup the code accordingly. - use netdev features instead of flags bit to check for the configured offload - drop packet in case of enabled features/configured hdr size mismatch v2 -> v3: - cleaned-up uAPI comments - use explicit struct layout instead of raw buf. --- drivers/net/tun.c | 58 +++++++++++++++++++++---- drivers/net/tun_vnet.h | 101 ++++++++++++++++++++++++++++++++++++++++---- include/uapi/linux/if_tun.h | 9 ++++ 3 files changed, 150 insertions(+), 18 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/tun.c b/drivers/net/tun.c index f8c5e2fd04df..abc91f28dac4 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -186,7 +186,8 @@ struct tun_struct { struct net_device *dev; netdev_features_t set_features; #define TUN_USER_FEATURES (NETIF_F_HW_CSUM|NETIF_F_TSO_ECN|NETIF_F_TSO| \ - NETIF_F_TSO6 | NETIF_F_GSO_UDP_L4) + NETIF_F_TSO6 | NETIF_F_GSO_UDP_L4 | \ + NETIF_F_GSO_UDP_TUNNEL | NETIF_F_GSO_UDP_TUNNEL_CSUM) int align; int vnet_hdr_sz; @@ -925,6 +926,7 @@ static int tun_net_init(struct net_device *dev) dev->hw_features = NETIF_F_SG | NETIF_F_FRAGLIST | TUN_USER_FEATURES | NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_STAG_TX; + dev->hw_enc_features = dev->hw_features; dev->features = dev->hw_features; dev->vlan_features = dev->features & ~(NETIF_F_HW_VLAN_CTAG_TX | @@ -1698,7 +1700,8 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, struct sk_buff *skb; size_t total_len = iov_iter_count(from); size_t len = total_len, align = tun->align, linear; - struct virtio_net_hdr gso = { 0 }; + struct virtio_net_hdr_v1_hash_tunnel hdr; + struct virtio_net_hdr *gso; int good_linear; int copylen; int hdr_len = 0; @@ -1708,6 +1711,15 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, int skb_xdp = 1; bool frags = tun_napi_frags_enabled(tfile); enum skb_drop_reason drop_reason = SKB_DROP_REASON_NOT_SPECIFIED; + netdev_features_t features = 0; + + /* + * Keep it easy and always zero the whole buffer, even if the + * tunnel-related field will be touched only when the feature + * is enabled and the hdr size id compatible. + */ + memset(&hdr, 0, sizeof(hdr)); + gso = (struct virtio_net_hdr *)&hdr; if (!(tun->flags & IFF_NO_PI)) { if (len < sizeof(pi)) @@ -1721,7 +1733,9 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, if (tun->flags & IFF_VNET_HDR) { int vnet_hdr_sz = READ_ONCE(tun->vnet_hdr_sz); - hdr_len = tun_vnet_hdr_get(vnet_hdr_sz, tun->flags, from, &gso); + features = tun_vnet_hdr_guest_features(vnet_hdr_sz); + hdr_len = __tun_vnet_hdr_get(vnet_hdr_sz, tun->flags, + features, from, gso); if (hdr_len < 0) return hdr_len; @@ -1755,7 +1769,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, * (e.g gso or jumbo packet), we will do it at after * skb was created with generic XDP routine. */ - skb = tun_build_skb(tun, tfile, from, &gso, len, &skb_xdp); + skb = tun_build_skb(tun, tfile, from, gso, len, &skb_xdp); err = PTR_ERR_OR_ZERO(skb); if (err) goto drop; @@ -1799,7 +1813,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } - if (tun_vnet_hdr_to_skb(tun->flags, skb, &gso)) { + if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { atomic_long_inc(&tun->rx_frame_errors); err = -EINVAL; goto free_skb; @@ -2050,13 +2064,21 @@ static ssize_t tun_put_user(struct tun_struct *tun, } if (vnet_hdr_sz) { - struct virtio_net_hdr gso; + struct virtio_net_hdr_v1_hash_tunnel hdr; + struct virtio_net_hdr *gso; - ret = tun_vnet_hdr_from_skb(tun->flags, tun->dev, skb, &gso); + ret = tun_vnet_hdr_tnl_from_skb(tun->flags, tun->dev, skb, + &hdr); if (ret) return ret; - ret = tun_vnet_hdr_put(vnet_hdr_sz, iter, &gso); + /* + * Drop the packet if the configured header size is too small + * WRT the enabled offloads. + */ + gso = (struct virtio_net_hdr *)&hdr; + ret = __tun_vnet_hdr_put(vnet_hdr_sz, tun->dev->features, + iter, gso); if (ret) return ret; } @@ -2357,10 +2379,12 @@ static int tun_xdp_one(struct tun_struct *tun, { unsigned int datasize = xdp->data_end - xdp->data; struct tun_xdp_hdr *hdr = xdp->data_hard_start; + struct virtio_net_hdr_v1_hash_tunnel *tnl_hdr; struct virtio_net_hdr *gso = &hdr->gso; struct bpf_prog *xdp_prog; struct sk_buff *skb = NULL; struct sk_buff_head *queue; + netdev_features_t features; u32 rxhash = 0, act; int buflen = hdr->buflen; int metasize = 0; @@ -2426,7 +2450,9 @@ build: if (metasize > 0) skb_metadata_set(skb, metasize); - if (tun_vnet_hdr_to_skb(tun->flags, skb, gso)) { + features = tun_vnet_hdr_guest_features(READ_ONCE(tun->vnet_hdr_sz)); + tnl_hdr = (struct virtio_net_hdr_v1_hash_tunnel *)gso; + if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, tnl_hdr)) { atomic_long_inc(&tun->rx_frame_errors); kfree_skb(skb); ret = -EINVAL; @@ -2812,6 +2838,8 @@ static void tun_get_iff(struct tun_struct *tun, struct ifreq *ifr) } +#define PLAIN_GSO (NETIF_F_GSO_UDP_L4 | NETIF_F_TSO | NETIF_F_TSO6) + /* This is like a cut-down ethtool ops, except done via tun fd so no * privs required. */ static int set_offload(struct tun_struct *tun, unsigned long arg) @@ -2841,6 +2869,18 @@ static int set_offload(struct tun_struct *tun, unsigned long arg) features |= NETIF_F_GSO_UDP_L4; arg &= ~(TUN_F_USO4 | TUN_F_USO6); } + + /* + * Tunnel offload is allowed only if some plain offload is + * available, too. + */ + if (features & PLAIN_GSO && arg & TUN_F_UDP_TUNNEL_GSO) { + features |= NETIF_F_GSO_UDP_TUNNEL; + if (arg & TUN_F_UDP_TUNNEL_GSO_CSUM) + features |= NETIF_F_GSO_UDP_TUNNEL_CSUM; + arg &= ~(TUN_F_UDP_TUNNEL_GSO | + TUN_F_UDP_TUNNEL_GSO_CSUM); + } } /* This gives the user a way to test for new features in future by diff --git a/drivers/net/tun_vnet.h b/drivers/net/tun_vnet.h index 58b9ac7a5fc4..81662328b2c7 100644 --- a/drivers/net/tun_vnet.h +++ b/drivers/net/tun_vnet.h @@ -6,6 +6,8 @@ #define TUN_VNET_LE 0x80000000 #define TUN_VNET_BE 0x40000000 +#define TUN_VNET_TNL_SIZE sizeof(struct virtio_net_hdr_v1_hash_tunnel) + static inline bool tun_vnet_legacy_is_little_endian(unsigned int flags) { bool be = IS_ENABLED(CONFIG_TUN_VNET_CROSS_LE) && @@ -107,16 +109,26 @@ static inline long tun_vnet_ioctl(int *vnet_hdr_sz, unsigned int *flags, } } -static inline int tun_vnet_hdr_get(int sz, unsigned int flags, - struct iov_iter *from, - struct virtio_net_hdr *hdr) +static inline unsigned int tun_vnet_parse_size(netdev_features_t features) +{ + if (!(features & NETIF_F_GSO_UDP_TUNNEL)) + return sizeof(struct virtio_net_hdr); + + return TUN_VNET_TNL_SIZE; +} + +static inline int __tun_vnet_hdr_get(int sz, unsigned int flags, + netdev_features_t features, + struct iov_iter *from, + struct virtio_net_hdr *hdr) { + unsigned int parsed_size = tun_vnet_parse_size(features); u16 hdr_len; if (iov_iter_count(from) < sz) return -EINVAL; - if (!copy_from_iter_full(hdr, sizeof(*hdr), from)) + if (!copy_from_iter_full(hdr, parsed_size, from)) return -EFAULT; hdr_len = tun_vnet16_to_cpu(flags, hdr->hdr_len); @@ -129,32 +141,70 @@ static inline int tun_vnet_hdr_get(int sz, unsigned int flags, if (hdr_len > iov_iter_count(from)) return -EINVAL; - iov_iter_advance(from, sz - sizeof(*hdr)); + iov_iter_advance(from, sz - parsed_size); return hdr_len; } -static inline int tun_vnet_hdr_put(int sz, struct iov_iter *iter, - const struct virtio_net_hdr *hdr) +static inline int tun_vnet_hdr_get(int sz, unsigned int flags, + struct iov_iter *from, + struct virtio_net_hdr *hdr) +{ + return __tun_vnet_hdr_get(sz, flags, 0, from, hdr); +} + +static inline int __tun_vnet_hdr_put(int sz, netdev_features_t features, + struct iov_iter *iter, + const struct virtio_net_hdr *hdr) { + unsigned int parsed_size = tun_vnet_parse_size(features); + if (unlikely(iov_iter_count(iter) < sz)) return -EINVAL; - if (unlikely(copy_to_iter(hdr, sizeof(*hdr), iter) != sizeof(*hdr))) + if (unlikely(copy_to_iter(hdr, parsed_size, iter) != parsed_size)) return -EFAULT; - if (iov_iter_zero(sz - sizeof(*hdr), iter) != sz - sizeof(*hdr)) + if (iov_iter_zero(sz - parsed_size, iter) != sz - parsed_size) return -EFAULT; return 0; } +static inline int tun_vnet_hdr_put(int sz, struct iov_iter *iter, + const struct virtio_net_hdr *hdr) +{ + return __tun_vnet_hdr_put(sz, 0, iter, hdr); +} + static inline int tun_vnet_hdr_to_skb(unsigned int flags, struct sk_buff *skb, const struct virtio_net_hdr *hdr) { return virtio_net_hdr_to_skb(skb, hdr, tun_vnet_is_little_endian(flags)); } +/* + * Tun is not aware of the negotiated guest features, guess them from the + * virtio net hdr size + */ +static inline netdev_features_t tun_vnet_hdr_guest_features(int vnet_hdr_sz) +{ + if (vnet_hdr_sz >= TUN_VNET_TNL_SIZE) + return NETIF_F_GSO_UDP_TUNNEL | NETIF_F_GSO_UDP_TUNNEL_CSUM; + return 0; +} + +static inline int +tun_vnet_hdr_tnl_to_skb(unsigned int flags, netdev_features_t features, + struct sk_buff *skb, + const struct virtio_net_hdr_v1_hash_tunnel *hdr) +{ + return virtio_net_hdr_tnl_to_skb(skb, hdr, + features & NETIF_F_GSO_UDP_TUNNEL, + features & NETIF_F_GSO_UDP_TUNNEL_CSUM, + tun_vnet_is_little_endian(flags)); +} + static inline int tun_vnet_hdr_from_skb(unsigned int flags, const struct net_device *dev, const struct sk_buff *skb, @@ -183,4 +233,37 @@ static inline int tun_vnet_hdr_from_skb(unsigned int flags, return 0; } +static inline int +tun_vnet_hdr_tnl_from_skb(unsigned int flags, + const struct net_device *dev, + const struct sk_buff *skb, + struct virtio_net_hdr_v1_hash_tunnel *tnl_hdr) +{ + bool has_tnl_offload = !!(dev->features & NETIF_F_GSO_UDP_TUNNEL); + int vlan_hlen = skb_vlan_tag_present(skb) ? VLAN_HLEN : 0; + + if (virtio_net_hdr_tnl_from_skb(skb, tnl_hdr, has_tnl_offload, + tun_vnet_is_little_endian(flags), + vlan_hlen)) { + struct virtio_net_hdr_v1 *hdr = &tnl_hdr->hash_hdr.hdr; + struct skb_shared_info *sinfo = skb_shinfo(skb); + + if (net_ratelimit()) { + int hdr_len = tun_vnet16_to_cpu(flags, hdr->hdr_len); + + netdev_err(dev, "unexpected GSO type: 0x%x, gso_size %d, hdr_len %d\n", + sinfo->gso_type, + tun_vnet16_to_cpu(flags, hdr->gso_size), + tun_vnet16_to_cpu(flags, hdr->hdr_len)); + print_hex_dump(KERN_ERR, "tun: ", DUMP_PREFIX_NONE, + 16, 1, skb->head, min(hdr_len, 64), + true); + } + WARN_ON_ONCE(1); + return -EINVAL; + } + + return 0; +} + #endif /* TUN_VNET_H */ diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h index 287cdc81c939..79d53c7a1ebd 100644 --- a/include/uapi/linux/if_tun.h +++ b/include/uapi/linux/if_tun.h @@ -93,6 +93,15 @@ #define TUN_F_USO4 0x20 /* I can handle USO for IPv4 packets */ #define TUN_F_USO6 0x40 /* I can handle USO for IPv6 packets */ +/* I can handle TSO/USO for UDP tunneled packets */ +#define TUN_F_UDP_TUNNEL_GSO 0x080 + +/* + * I can handle TSO/USO for UDP tunneled packets requiring csum offload for + * the outer header + */ +#define TUN_F_UDP_TUNNEL_GSO_CSUM 0x100 + /* Protocol info prepended to the packets (when IFF_NO_PI is not set) */ #define TUN_PKT_STRIP 0x0001 struct tun_pi { -- cgit v1.2.3 From 45e359be1ce88fb22e61fa3aa23b2e450a6cae03 Mon Sep 17 00:00:00 2001 From: Jason Xing Date: Sat, 5 Jul 2025 00:01:38 +0800 Subject: net: xsk: introduce XDP_MAX_TX_SKB_BUDGET setsockopt This patch provides a setsockopt method to let applications leverage to adjust how many descs to be handled at most in one send syscall. It mitigates the situation where the default value (32) that is too small leads to higher frequency of triggering send syscall. Considering the prosperity/complexity the applications have, there is no absolutely ideal suggestion fitting all cases. So keep 32 as its default value like before. The patch does the following things: - Add XDP_MAX_TX_SKB_BUDGET socket option. - Set max_tx_budget to 32 by default in the initialization phase as a per-socket granular control. - Set the range of max_tx_budget as [32, xs->tx->nentries]. The idea behind this comes out of real workloads in production. We use a user-level stack with xsk support to accelerate sending packets and minimize triggering syscalls. When the packets are aggregated, it's not hard to hit the upper bound (namely, 32). The moment user-space stack fetches the -EAGAIN error number passed from sendto(), it will loop to try again until all the expected descs from tx ring are sent out to the driver. Enlarging the XDP_MAX_TX_SKB_BUDGET value contributes to less frequency of sendto() and higher throughput/PPS. Here is what I did in production, along with some numbers as follows: For one application I saw lately, I suggested using 128 as max_tx_budget because I saw two limitations without changing any default configuration: 1) XDP_MAX_TX_SKB_BUDGET, 2) socket sndbuf which is 212992 decided by net.core.wmem_default. As to XDP_MAX_TX_SKB_BUDGET, the scenario behind this was I counted how many descs are transmitted to the driver at one time of sendto() based on [1] patch and then I calculated the possibility of hitting the upper bound. Finally I chose 128 as a suitable value because 1) it covers most of the cases, 2) a higher number would not bring evident results. After twisting the parameters, a stable improvement of around 4% for both PPS and throughput and less resources consumption were found to be observed by strace -c -p xxx: 1) %time was decreased by 7.8% 2) error counter was decreased from 18367 to 572 [1]: https://lore.kernel.org/all/20250619093641.70700-1-kerneljasonxing@gmail.com/ Signed-off-by: Jason Xing Acked-by: Maciej Fijalkowski Link: https://patch.msgid.link/20250704160138.48677-1-kerneljasonxing@gmail.com Signed-off-by: Paolo Abeni --- Documentation/networking/af_xdp.rst | 9 +++++++++ include/net/xdp_sock.h | 1 + include/uapi/linux/if_xdp.h | 1 + net/xdp/xsk.c | 21 +++++++++++++++++++-- tools/include/uapi/linux/if_xdp.h | 1 + 5 files changed, 31 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/networking/af_xdp.rst b/Documentation/networking/af_xdp.rst index d486014bb31d..50d92084a49c 100644 --- a/Documentation/networking/af_xdp.rst +++ b/Documentation/networking/af_xdp.rst @@ -438,6 +438,15 @@ is created by a privileged process and passed to a non-privileged one. Once the option is set, kernel will refuse attempts to bind that socket to a different interface. Updating the value requires CAP_NET_RAW. +XDP_MAX_TX_SKB_BUDGET setsockopt +-------------------------------- + +This setsockopt sets the maximum number of descriptors that can be handled +and passed to the driver at one send syscall. It is applied in the copy +mode to allow application to tune the per-socket maximum iteration for +better throughput and less frequency of send syscall. +Allowed range is [32, xs->tx->nentries]. + XDP_STATISTICS getsockopt ------------------------- diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h index e8bd6ddb7b12..ce587a225661 100644 --- a/include/net/xdp_sock.h +++ b/include/net/xdp_sock.h @@ -84,6 +84,7 @@ struct xdp_sock { struct list_head map_list; /* Protects map_list */ spinlock_t map_list_lock; + u32 max_tx_budget; /* Protects multiple processes in the control path */ struct mutex mutex; struct xsk_queue *fq_tmp; /* Only as tmp storage before bind */ diff --git a/include/uapi/linux/if_xdp.h b/include/uapi/linux/if_xdp.h index 44f2bb93e7e6..23a062781468 100644 --- a/include/uapi/linux/if_xdp.h +++ b/include/uapi/linux/if_xdp.h @@ -79,6 +79,7 @@ struct xdp_mmap_offsets { #define XDP_UMEM_COMPLETION_RING 6 #define XDP_STATISTICS 7 #define XDP_OPTIONS 8 +#define XDP_MAX_TX_SKB_BUDGET 9 struct xdp_umem_reg { __u64 addr; /* Start of packet data area */ diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index bd61b0bc9c24..9c3acecc14b1 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -34,7 +34,7 @@ #include "xsk.h" #define TX_BATCH_SIZE 32 -#define MAX_PER_SOCKET_BUDGET (TX_BATCH_SIZE) +#define MAX_PER_SOCKET_BUDGET 32 void xsk_set_rx_need_wakeup(struct xsk_buff_pool *pool) { @@ -783,10 +783,10 @@ free_err: static int __xsk_generic_xmit(struct sock *sk) { struct xdp_sock *xs = xdp_sk(sk); - u32 max_batch = TX_BATCH_SIZE; bool sent_frame = false; struct xdp_desc desc; struct sk_buff *skb; + u32 max_batch; int err = 0; mutex_lock(&xs->mutex); @@ -800,6 +800,7 @@ static int __xsk_generic_xmit(struct sock *sk) if (xs->queue_id >= xs->dev->real_num_tx_queues) goto out; + max_batch = READ_ONCE(xs->max_tx_budget); while (xskq_cons_peek_desc(xs->tx, &desc, xs->pool)) { if (max_batch-- == 0) { err = -EAGAIN; @@ -1440,6 +1441,21 @@ static int xsk_setsockopt(struct socket *sock, int level, int optname, mutex_unlock(&xs->mutex); return err; } + case XDP_MAX_TX_SKB_BUDGET: + { + unsigned int budget; + + if (optlen != sizeof(budget)) + return -EINVAL; + if (copy_from_sockptr(&budget, optval, sizeof(budget))) + return -EFAULT; + if (!xs->tx || + budget < TX_BATCH_SIZE || budget > xs->tx->nentries) + return -EACCES; + + WRITE_ONCE(xs->max_tx_budget, budget); + return 0; + } default: break; } @@ -1737,6 +1753,7 @@ static int xsk_create(struct net *net, struct socket *sock, int protocol, xs = xdp_sk(sk); xs->state = XSK_READY; + xs->max_tx_budget = TX_BATCH_SIZE; mutex_init(&xs->mutex); INIT_LIST_HEAD(&xs->map_list); diff --git a/tools/include/uapi/linux/if_xdp.h b/tools/include/uapi/linux/if_xdp.h index 44f2bb93e7e6..23a062781468 100644 --- a/tools/include/uapi/linux/if_xdp.h +++ b/tools/include/uapi/linux/if_xdp.h @@ -79,6 +79,7 @@ struct xdp_mmap_offsets { #define XDP_UMEM_COMPLETION_RING 6 #define XDP_STATISTICS 7 #define XDP_OPTIONS 8 +#define XDP_MAX_TX_SKB_BUDGET 9 struct xdp_umem_reg { __u64 addr; /* Start of packet data area */ -- cgit v1.2.3 From d7974697de4d6fa1a1ed9ca43616a8500046f25a Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 8 Jul 2025 15:06:38 -0700 Subject: ethtool: mark ETHER_FLOW as usable for Rx hash Looks like some drivers (ena, enetc, fbnic.. there's probably more) consider ETHER_FLOW to be legitimate target for flow hashing. I'm not sure how intentional that is from the uAPI perspective vs just an effect of ethtool IOCTL doing minimal input validation. But Netlink will do strict validation, so we need to decide whether we allow this use case or not. I don't see a strong reason against it, and rejecting it would potentially regress a number of drivers. So update the comments and flow_type_hashable(). Link: https://patch.msgid.link/20250708220640.2738464-4-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/uapi/linux/ethtool.h | 4 ++-- net/ethtool/ioctl.c | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index 707c1844010c..9e9afdd1238a 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -2314,7 +2314,7 @@ enum { IPV6_USER_FLOW = 0x0e, /* spec only (usr_ip6_spec; nfc only) */ IPV4_FLOW = 0x10, /* hash only */ IPV6_FLOW = 0x11, /* hash only */ - ETHER_FLOW = 0x12, /* spec only (ether_spec) */ + ETHER_FLOW = 0x12, /* hash or spec (ether_spec) */ /* Used for GTP-U IPv4 and IPv6. * The format of GTP packets only includes @@ -2371,7 +2371,7 @@ enum { /* Flag to enable RSS spreading of traffic matching rule (nfc only) */ #define FLOW_RSS 0x20000000 -/* L3-L4 network traffic flow hash options */ +/* L2-L4 network traffic flow hash options */ #define RXH_L2DA (1 << 1) #define RXH_VLAN (1 << 2) #define RXH_L3_PROTO (1 << 3) diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index 139f95620cdd..67f6d900a4ee 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -981,6 +981,7 @@ static int ethtool_rxnfc_copy_to_user(void __user *useraddr, static bool flow_type_hashable(u32 flow_type) { switch (flow_type) { + case ETHER_FLOW: case TCP_V4_FLOW: case UDP_V4_FLOW: case SCTP_V4_FLOW: -- cgit v1.2.3 From 178331743ca860561f60d04a7797a2fce13f0784 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 8 Jul 2025 15:06:39 -0700 Subject: ethtool: rss: report which fields are configured for hashing Implement ETHTOOL_GRXFH over Netlink. The number of flow types is reasonable (around 20) so report all of them at once for simplicity. Do not maintain the flow ID mapping with ioctl at the uAPI level. This gives us a chance to clean up the confusion that come from RxNFC vs RxFH (flow direction vs hashing) in the ioctl. Try to align with the names used in ethtool CLI, they seem to have stood the test of time just fine. One annoyance is that we still call L4 ports the weird names, but I guess they also apply to IPSec (where they cover the SPI) so it is what it is. $ ynl --family ethtool --dump rss-get { "header": { "dev-index": 1, "dev-name": "enp1s0" }, "hfunc": 1, "hkey": b"...", "indir": [0, 1, ...], "flow-hash": { "ether": {"l2da"}, "ah-esp4": {"ip-src", "ip-dst"}, "ah-esp6": {"ip-src", "ip-dst"}, "ah4": {"ip-src", "ip-dst"}, "ah6": {"ip-src", "ip-dst"}, "esp4": {"ip-src", "ip-dst"}, "esp6": {"ip-src", "ip-dst"}, "ip4": {"ip-src", "ip-dst"}, "ip6": {"ip-src", "ip-dst"}, "sctp4": {"ip-src", "ip-dst"}, "sctp6": {"ip-src", "ip-dst"}, "udp4": {"ip-src", "ip-dst"}, "udp6": {"ip-src", "ip-dst"} "tcp4": {"l4-b-0-1", "l4-b-2-3", "ip-src", "ip-dst"}, "tcp6": {"l4-b-0-1", "l4-b-2-3", "ip-src", "ip-dst"}, }, } Link: https://patch.msgid.link/20250708220640.2738464-5-kuba@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/ethtool.yaml | 151 +++++++++++++++++++++++++ Documentation/networking/ethtool-netlink.rst | 9 +- include/uapi/linux/ethtool_netlink_generated.h | 34 ++++++ net/ethtool/ioctl.c | 6 +- net/ethtool/rss.c | 105 +++++++++++++++-- 5 files changed, 291 insertions(+), 14 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index 49e782a33eb6..c38c03c624f0 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -158,6 +158,35 @@ definitions: - name: pse-event-sw-pw-control-error doc: PSE faced an error managing the power control from software + - + name: rxfh-fields + name-prefix: rxh- + enum-name: + header: linux/ethtool.h + type: flags + entries: + - + name: l2da + value: 1 + - + name: vlan + - + name: l3-proto + - + name: ip-src + - + name: ip-dst + - + name: l4-b-0-1 + doc: src port in case of TCP/UDP/SCTP + - + name: l4-b-2-3 + doc: dst port in case of TCP/UDP/SCTP + - + name: gtp-teid + - + name: discard + value: 31 attribute-sets: - @@ -1447,6 +1476,123 @@ attribute-sets: name: pse-prio type: u32 name-prefix: ethtool-a- + - + name: flow + attr-cnt-name: --ethtool-a-flow-cnt + doc: | + Flow types, corresponding to those defined in the old + ethtool header for RXFH and RXNFC as ${PROTO}_FLOW. + The values are not matching the old ones to avoid carrying + into Netlink the IP_USER_FLOW vs IPV4_FLOW vs IPV4_USER_FLOW confusion. + attributes: + - + name: ether + type: uint + enum: rxfh-fields + - + name: ip4 + type: uint + enum: rxfh-fields + - + name: ip6 + type: uint + enum: rxfh-fields + - + name: tcp4 + type: uint + enum: rxfh-fields + - + name: tcp6 + type: uint + enum: rxfh-fields + - + name: udp4 + type: uint + enum: rxfh-fields + - + name: udp6 + type: uint + enum: rxfh-fields + - + name: sctp4 + type: uint + enum: rxfh-fields + - + name: sctp6 + type: uint + enum: rxfh-fields + - + name: ah4 + type: uint + enum: rxfh-fields + - + name: ah6 + type: uint + enum: rxfh-fields + - + name: esp4 + type: uint + enum: rxfh-fields + - + name: esp6 + type: uint + enum: rxfh-fields + - + name: ah-esp4 + type: uint + enum: rxfh-fields + - + name: ah-esp6 + type: uint + enum: rxfh-fields + - + name: gtpu4 + type: uint + enum: rxfh-fields + - + name: gtpu6 + type: uint + enum: rxfh-fields + - + name: gtpc4 + type: uint + enum: rxfh-fields + - + name: gtpc6 + type: uint + enum: rxfh-fields + - + name: gtpc-teid4 + type: uint + enum: rxfh-fields + - + name: gtpc-teid6 + type: uint + enum: rxfh-fields + - + name: gtpu-eh4 + type: uint + enum: rxfh-fields + - + name: gtpu-eh6 + type: uint + enum: rxfh-fields + - + name: gtpu-ul4 + type: uint + enum: rxfh-fields + - + name: gtpu-ul6 + type: uint + enum: rxfh-fields + - + name: gtpu-dl4 + type: uint + enum: rxfh-fields + - + name: gtpu-dl6 + type: uint + enum: rxfh-fields - name: rss attr-cnt-name: __ethtool-a-rss-cnt @@ -1478,6 +1624,10 @@ attribute-sets: - name: start-context type: u32 + - + name: flow-hash + type: nest + nested-attributes: flow - name: plca attr-cnt-name: __ethtool-a-plca-cnt @@ -2307,6 +2457,7 @@ operations: - indir - hkey - input-xfrm + - flow-hash dump: request: attributes: diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index 07e9808ebd2c..248bc3d93da9 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -1969,14 +1969,15 @@ used to ignore context 0s and only dump additional contexts). Kernel response contents: -===================================== ====== ========================== +===================================== ====== =============================== ``ETHTOOL_A_RSS_HEADER`` nested reply header ``ETHTOOL_A_RSS_CONTEXT`` u32 context number ``ETHTOOL_A_RSS_HFUNC`` u32 RSS hash func ``ETHTOOL_A_RSS_INDIR`` binary Indir table bytes ``ETHTOOL_A_RSS_HKEY`` binary Hash key bytes ``ETHTOOL_A_RSS_INPUT_XFRM`` u32 RSS input data transformation -===================================== ====== ========================== + ``ETHTOOL_A_RSS_FLOW_HASH`` nested Header fields included in hash +===================================== ====== =============================== ETHTOOL_A_RSS_HFUNC attribute is bitmap indicating the hash function being used. Current supported options are toeplitz, xor or crc32. @@ -1985,6 +1986,8 @@ indicates queue number. ETHTOOL_A_RSS_INPUT_XFRM attribute is a bitmap indicating the type of transformation applied to the input protocol fields before given to the RSS hfunc. Current supported options are symmetric-xor and symmetric-or-xor. +ETHTOOL_A_RSS_FLOW_HASH carries per-flow type bitmask of which header +fields are included in the hash calculation. PLCA_GET_CFG ============ @@ -2436,7 +2439,7 @@ are netlink only. ``ETHTOOL_SFLAGS`` ``ETHTOOL_MSG_FEATURES_SET`` ``ETHTOOL_GPFLAGS`` ``ETHTOOL_MSG_PRIVFLAGS_GET`` ``ETHTOOL_SPFLAGS`` ``ETHTOOL_MSG_PRIVFLAGS_SET`` - ``ETHTOOL_GRXFH`` n/a + ``ETHTOOL_GRXFH`` ``ETHTOOL_MSG_RSS_GET`` ``ETHTOOL_SRXFH`` n/a ``ETHTOOL_GGRO`` ``ETHTOOL_MSG_FEATURES_GET`` ``ETHTOOL_SGRO`` ``ETHTOOL_MSG_FEATURES_SET`` diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 8f30ffa1cd14..96027e26ffba 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -678,6 +678,39 @@ enum { ETHTOOL_A_PSE_MAX = (__ETHTOOL_A_PSE_CNT - 1) }; +enum { + ETHTOOL_A_FLOW_ETHER = 1, + ETHTOOL_A_FLOW_IP4, + ETHTOOL_A_FLOW_IP6, + ETHTOOL_A_FLOW_TCP4, + ETHTOOL_A_FLOW_TCP6, + ETHTOOL_A_FLOW_UDP4, + ETHTOOL_A_FLOW_UDP6, + ETHTOOL_A_FLOW_SCTP4, + ETHTOOL_A_FLOW_SCTP6, + ETHTOOL_A_FLOW_AH4, + ETHTOOL_A_FLOW_AH6, + ETHTOOL_A_FLOW_ESP4, + ETHTOOL_A_FLOW_ESP6, + ETHTOOL_A_FLOW_AH_ESP4, + ETHTOOL_A_FLOW_AH_ESP6, + ETHTOOL_A_FLOW_GTPU4, + ETHTOOL_A_FLOW_GTPU6, + ETHTOOL_A_FLOW_GTPC4, + ETHTOOL_A_FLOW_GTPC6, + ETHTOOL_A_FLOW_GTPC_TEID4, + ETHTOOL_A_FLOW_GTPC_TEID6, + ETHTOOL_A_FLOW_GTPU_EH4, + ETHTOOL_A_FLOW_GTPU_EH6, + ETHTOOL_A_FLOW_GTPU_UL4, + ETHTOOL_A_FLOW_GTPU_UL6, + ETHTOOL_A_FLOW_GTPU_DL4, + ETHTOOL_A_FLOW_GTPU_DL6, + + __ETHTOOL_A_FLOW_CNT, + ETHTOOL_A_FLOW_MAX = (__ETHTOOL_A_FLOW_CNT - 1) +}; + enum { ETHTOOL_A_RSS_UNSPEC, ETHTOOL_A_RSS_HEADER, @@ -687,6 +720,7 @@ enum { ETHTOOL_A_RSS_HKEY, ETHTOOL_A_RSS_INPUT_XFRM, ETHTOOL_A_RSS_START_CONTEXT, + ETHTOOL_A_RSS_FLOW_HASH, __ETHTOOL_A_RSS_CNT, ETHTOOL_A_RSS_MAX = (__ETHTOOL_A_RSS_CNT - 1) diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index 67f6d900a4ee..cccb4694f5e1 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -1101,7 +1101,11 @@ ethtool_set_rxfh_fields(struct net_device *dev, u32 cmd, void __user *useraddr) rc = ops->set_rxfh_fields(dev, &fields, NULL); exit_unlock: mutex_unlock(&dev->ethtool->rss_lock); - return rc; + if (rc) + return rc; + + ethtool_rss_notify(dev, fields.rss_context); + return 0; } static noinline_for_stack int diff --git a/net/ethtool/rss.c b/net/ethtool/rss.c index 37a7b20fcd07..41ab9fc67652 100644 --- a/net/ethtool/rss.c +++ b/net/ethtool/rss.c @@ -12,6 +12,7 @@ struct rss_req_info { struct rss_reply_data { struct ethnl_reply_data base; + bool has_flow_hash; bool no_key_fields; u32 indir_size; u32 hkey_size; @@ -19,6 +20,37 @@ struct rss_reply_data { u32 input_xfrm; u32 *indir_table; u8 *hkey; + int flow_hash[__ETHTOOL_A_FLOW_CNT]; +}; + +static const u8 ethtool_rxfh_ft_nl2ioctl[] = { + [ETHTOOL_A_FLOW_ETHER] = ETHER_FLOW, + [ETHTOOL_A_FLOW_IP4] = IPV4_FLOW, + [ETHTOOL_A_FLOW_IP6] = IPV6_FLOW, + [ETHTOOL_A_FLOW_TCP4] = TCP_V4_FLOW, + [ETHTOOL_A_FLOW_UDP4] = UDP_V4_FLOW, + [ETHTOOL_A_FLOW_SCTP4] = SCTP_V4_FLOW, + [ETHTOOL_A_FLOW_AH_ESP4] = AH_ESP_V4_FLOW, + [ETHTOOL_A_FLOW_TCP6] = TCP_V6_FLOW, + [ETHTOOL_A_FLOW_UDP6] = UDP_V6_FLOW, + [ETHTOOL_A_FLOW_SCTP6] = SCTP_V6_FLOW, + [ETHTOOL_A_FLOW_AH_ESP6] = AH_ESP_V6_FLOW, + [ETHTOOL_A_FLOW_AH4] = AH_V4_FLOW, + [ETHTOOL_A_FLOW_ESP4] = ESP_V4_FLOW, + [ETHTOOL_A_FLOW_AH6] = AH_V6_FLOW, + [ETHTOOL_A_FLOW_ESP6] = ESP_V6_FLOW, + [ETHTOOL_A_FLOW_GTPU4] = GTPU_V4_FLOW, + [ETHTOOL_A_FLOW_GTPU6] = GTPU_V6_FLOW, + [ETHTOOL_A_FLOW_GTPC4] = GTPC_V4_FLOW, + [ETHTOOL_A_FLOW_GTPC6] = GTPC_V6_FLOW, + [ETHTOOL_A_FLOW_GTPC_TEID4] = GTPC_TEID_V4_FLOW, + [ETHTOOL_A_FLOW_GTPC_TEID6] = GTPC_TEID_V6_FLOW, + [ETHTOOL_A_FLOW_GTPU_EH4] = GTPU_EH_V4_FLOW, + [ETHTOOL_A_FLOW_GTPU_EH6] = GTPU_EH_V6_FLOW, + [ETHTOOL_A_FLOW_GTPU_UL4] = GTPU_UL_V4_FLOW, + [ETHTOOL_A_FLOW_GTPU_UL6] = GTPU_UL_V6_FLOW, + [ETHTOOL_A_FLOW_GTPU_DL4] = GTPU_DL_V4_FLOW, + [ETHTOOL_A_FLOW_GTPU_DL6] = GTPU_DL_V6_FLOW, }; #define RSS_REQINFO(__req_base) \ @@ -49,6 +81,37 @@ rss_parse_request(struct ethnl_req_info *req_info, struct nlattr **tb, return 0; } +static void +rss_prepare_flow_hash(const struct rss_req_info *req, struct net_device *dev, + struct rss_reply_data *data, const struct genl_info *info) +{ + int i; + + data->has_flow_hash = false; + + if (!dev->ethtool_ops->get_rxfh_fields) + return; + if (req->rss_context && !dev->ethtool_ops->rxfh_per_ctx_fields) + return; + + mutex_lock(&dev->ethtool->rss_lock); + for (i = 1; i < __ETHTOOL_A_FLOW_CNT; i++) { + struct ethtool_rxfh_fields fields = { + .flow_type = ethtool_rxfh_ft_nl2ioctl[i], + .rss_context = req->rss_context, + }; + + if (dev->ethtool_ops->get_rxfh_fields(dev, &fields)) { + data->flow_hash[i] = -1; /* Unsupported */ + continue; + } + + data->flow_hash[i] = fields.data; + data->has_flow_hash = true; + } + mutex_unlock(&dev->ethtool->rss_lock); +} + static int rss_prepare_get(const struct rss_req_info *request, struct net_device *dev, struct rss_reply_data *data, const struct genl_info *info) @@ -153,6 +216,8 @@ static int rss_prepare(const struct rss_req_info *request, struct net_device *dev, struct rss_reply_data *data, const struct genl_info *info) { + rss_prepare_flow_hash(request, dev, data, info); + if (request->rss_context) return rss_prepare_ctx(request, dev, data, info); return rss_prepare_get(request, dev, data, info); @@ -190,7 +255,10 @@ rss_reply_size(const struct ethnl_req_info *req_base, nla_total_size(sizeof(u32)) + /* _RSS_HFUNC */ nla_total_size(sizeof(u32)) + /* _RSS_INPUT_XFRM */ nla_total_size(sizeof(u32) * data->indir_size) + /* _RSS_INDIR */ - nla_total_size(data->hkey_size); /* _RSS_HKEY */ + nla_total_size(data->hkey_size) + /* _RSS_HKEY */ + nla_total_size(0) + /* _RSS_FLOW_HASH */ + nla_total_size(sizeof(u32)) * ETHTOOL_A_FLOW_MAX + + 0; return len; } @@ -211,17 +279,34 @@ rss_fill_reply(struct sk_buff *skb, const struct ethnl_req_info *req_base, sizeof(u32) * data->indir_size, data->indir_table))) return -EMSGSIZE; - if (data->no_key_fields) - return 0; - - if ((data->hfunc && - nla_put_u32(skb, ETHTOOL_A_RSS_HFUNC, data->hfunc)) || - (data->input_xfrm && - nla_put_u32(skb, ETHTOOL_A_RSS_INPUT_XFRM, data->input_xfrm)) || - (data->hkey_size && - nla_put(skb, ETHTOOL_A_RSS_HKEY, data->hkey_size, data->hkey))) + if (!data->no_key_fields && + ((data->hfunc && + nla_put_u32(skb, ETHTOOL_A_RSS_HFUNC, data->hfunc)) || + (data->input_xfrm && + nla_put_u32(skb, ETHTOOL_A_RSS_INPUT_XFRM, data->input_xfrm)) || + (data->hkey_size && + nla_put(skb, ETHTOOL_A_RSS_HKEY, data->hkey_size, data->hkey)))) return -EMSGSIZE; + if (data->has_flow_hash) { + struct nlattr *nest; + int i; + + nest = nla_nest_start(skb, ETHTOOL_A_RSS_FLOW_HASH); + if (!nest) + return -EMSGSIZE; + + for (i = 1; i < __ETHTOOL_A_FLOW_CNT; i++) { + if (data->flow_hash[i] >= 0 && + nla_put_uint(skb, i, data->flow_hash[i])) { + nla_nest_cancel(skb, nest); + return -EMSGSIZE; + } + } + + nla_nest_end(skb, nest); + } + return 0; } -- cgit v1.2.3 From 2677010e7793451c20d895c477c4dc76f6e6a10e Mon Sep 17 00:00:00 2001 From: Samiullah Khawaja Date: Thu, 10 Jul 2025 21:12:03 +0000 Subject: Add support to set NAPI threaded for individual NAPI A net device has a threaded sysctl that can be used to enable threaded NAPI polling on all of the NAPI contexts under that device. Allow enabling threaded NAPI polling at individual NAPI level using netlink. Extend the netlink operation `napi-set` and allow setting the threaded attribute of a NAPI. This will enable the threaded polling on a NAPI context. Add a test in `nl_netdev.py` that verifies various cases of threaded NAPI being set at NAPI and at device level. Tested ./tools/testing/selftests/net/nl_netdev.py TAP version 13 1..7 ok 1 nl_netdev.empty_check ok 2 nl_netdev.lo_check ok 3 nl_netdev.page_pool_check ok 4 nl_netdev.napi_list_check ok 5 nl_netdev.dev_set_threaded ok 6 nl_netdev.napi_set_threaded ok 7 nl_netdev.nsim_rxq_reset_down # Totals: pass:7 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Samiullah Khawaja Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20250710211203.3979655-1-skhawaja@google.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/netdev.yaml | 10 ++++ Documentation/networking/napi.rst | 9 +++- include/linux/netdevice.h | 1 + include/uapi/linux/netdev.h | 1 + net/core/dev.c | 30 +++++++++-- net/core/dev.h | 7 +++ net/core/netdev-genl-gen.c | 5 +- net/core/netdev-genl.c | 14 +++++ tools/include/uapi/linux/netdev.h | 1 + tools/testing/selftests/net/nl_netdev.py | 91 +++++++++++++++++++++++++++++++- 10 files changed, 162 insertions(+), 7 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index ce4cfec82100..85d0ea6ac426 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -283,6 +283,14 @@ attribute-sets: doc: The timeout, in nanoseconds, of how long to suspend irq processing, if event polling finds events type: uint + - + name: threaded + doc: Whether the NAPI is configured to operate in threaded polling + mode. If this is set to 1 then the NAPI context operates in + threaded polling mode. + type: uint + checks: + max: 1 - name: xsk-info attributes: [] @@ -694,6 +702,7 @@ operations: - defer-hard-irqs - gro-flush-timeout - irq-suspend-timeout + - threaded dump: request: attributes: @@ -746,6 +755,7 @@ operations: - defer-hard-irqs - gro-flush-timeout - irq-suspend-timeout + - threaded - name: bind-tx doc: Bind dmabuf to netdev for TX diff --git a/Documentation/networking/napi.rst b/Documentation/networking/napi.rst index d0e3953cae6a..a15754adb041 100644 --- a/Documentation/networking/napi.rst +++ b/Documentation/networking/napi.rst @@ -444,7 +444,14 @@ dependent). The NAPI instance IDs will be assigned in the opposite order than the process IDs of the kernel threads. Threaded NAPI is controlled by writing 0/1 to the ``threaded`` file in -netdev's sysfs directory. +netdev's sysfs directory. It can also be enabled for a specific NAPI using +netlink interface. + +For example, using the script: + +.. code-block:: bash + + $ ynl --family netdev --do napi-set --json='{"id": 66, "threaded": 1}' .. rubric:: Footnotes diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index ec23cee5245d..e49d8c98d284 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -369,6 +369,7 @@ struct napi_config { u64 irq_suspend_timeout; u32 defer_hard_irqs; cpumask_t affinity_mask; + bool threaded; unsigned int napi_id; }; diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h index 7eb9571786b8..1f3719a9a0eb 100644 --- a/include/uapi/linux/netdev.h +++ b/include/uapi/linux/netdev.h @@ -134,6 +134,7 @@ enum { NETDEV_A_NAPI_DEFER_HARD_IRQS, NETDEV_A_NAPI_GRO_FLUSH_TIMEOUT, NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT, + NETDEV_A_NAPI_THREADED, __NETDEV_A_NAPI_MAX, NETDEV_A_NAPI_MAX = (__NETDEV_A_NAPI_MAX - 1) diff --git a/net/core/dev.c b/net/core/dev.c index 19ddc3e6990a..621a639aeba1 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -6961,6 +6961,31 @@ static void napi_stop_kthread(struct napi_struct *napi) napi->thread = NULL; } +int napi_set_threaded(struct napi_struct *napi, bool threaded) +{ + if (threaded) { + if (!napi->thread) { + int err = napi_kthread_create(napi); + + if (err) + return err; + } + } + + if (napi->config) + napi->config->threaded = threaded; + + if (!threaded && napi->thread) { + napi_stop_kthread(napi); + } else { + /* Make sure kthread is created before THREADED bit is set. */ + smp_mb__before_atomic(); + assign_bit(NAPI_STATE_THREADED, &napi->state, threaded); + } + + return 0; +} + int dev_set_threaded(struct net_device *dev, bool threaded) { struct napi_struct *napi; @@ -6968,9 +6993,6 @@ int dev_set_threaded(struct net_device *dev, bool threaded) netdev_assert_locked_or_invisible(dev); - if (dev->threaded == threaded) - return 0; - if (threaded) { list_for_each_entry(napi, &dev->napi_list, dev_list) { if (!napi->thread) { @@ -7221,6 +7243,8 @@ static void napi_restore_config(struct napi_struct *n) napi_hash_add(n); n->config->napi_id = n->napi_id; } + + WARN_ON_ONCE(napi_set_threaded(n, n->config->threaded)); } static void napi_save_config(struct napi_struct *n) diff --git a/net/core/dev.h b/net/core/dev.h index e93f36b7ddf3..a603387fb566 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -315,6 +315,13 @@ static inline void napi_set_irq_suspend_timeout(struct napi_struct *n, WRITE_ONCE(n->irq_suspend_timeout, timeout); } +static inline bool napi_get_threaded(struct napi_struct *n) +{ + return test_bit(NAPI_STATE_THREADED, &n->state); +} + +int napi_set_threaded(struct napi_struct *n, bool threaded); + int rps_cpumask_housekeeping(struct cpumask *mask); #if defined(CONFIG_DEBUG_NET) && defined(CONFIG_BPF_SYSCALL) diff --git a/net/core/netdev-genl-gen.c b/net/core/netdev-genl-gen.c index 4fc44587f493..0994bd68a7e6 100644 --- a/net/core/netdev-genl-gen.c +++ b/net/core/netdev-genl-gen.c @@ -92,11 +92,12 @@ static const struct nla_policy netdev_bind_rx_nl_policy[NETDEV_A_DMABUF_FD + 1] }; /* NETDEV_CMD_NAPI_SET - do */ -static const struct nla_policy netdev_napi_set_nl_policy[NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT + 1] = { +static const struct nla_policy netdev_napi_set_nl_policy[NETDEV_A_NAPI_THREADED + 1] = { [NETDEV_A_NAPI_ID] = { .type = NLA_U32, }, [NETDEV_A_NAPI_DEFER_HARD_IRQS] = NLA_POLICY_FULL_RANGE(NLA_U32, &netdev_a_napi_defer_hard_irqs_range), [NETDEV_A_NAPI_GRO_FLUSH_TIMEOUT] = { .type = NLA_UINT, }, [NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT] = { .type = NLA_UINT, }, + [NETDEV_A_NAPI_THREADED] = NLA_POLICY_MAX(NLA_UINT, 1), }; /* NETDEV_CMD_BIND_TX - do */ @@ -193,7 +194,7 @@ static const struct genl_split_ops netdev_nl_ops[] = { .cmd = NETDEV_CMD_NAPI_SET, .doit = netdev_nl_napi_set_doit, .policy = netdev_napi_set_nl_policy, - .maxattr = NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT, + .maxattr = NETDEV_A_NAPI_THREADED, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index 2afa7b2141aa..5875df372415 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -184,6 +184,10 @@ netdev_nl_napi_fill_one(struct sk_buff *rsp, struct napi_struct *napi, if (napi->irq >= 0 && nla_put_u32(rsp, NETDEV_A_NAPI_IRQ, napi->irq)) goto nla_put_failure; + if (nla_put_uint(rsp, NETDEV_A_NAPI_THREADED, + napi_get_threaded(napi))) + goto nla_put_failure; + if (napi->thread) { pid = task_pid_nr(napi->thread); if (nla_put_u32(rsp, NETDEV_A_NAPI_PID, pid)) @@ -322,8 +326,18 @@ netdev_nl_napi_set_config(struct napi_struct *napi, struct genl_info *info) { u64 irq_suspend_timeout = 0; u64 gro_flush_timeout = 0; + u8 threaded = 0; u32 defer = 0; + if (info->attrs[NETDEV_A_NAPI_THREADED]) { + int ret; + + threaded = nla_get_uint(info->attrs[NETDEV_A_NAPI_THREADED]); + ret = napi_set_threaded(napi, !!threaded); + if (ret) + return ret; + } + if (info->attrs[NETDEV_A_NAPI_DEFER_HARD_IRQS]) { defer = nla_get_u32(info->attrs[NETDEV_A_NAPI_DEFER_HARD_IRQS]); napi_set_defer_hard_irqs(napi, defer); diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h index 7eb9571786b8..1f3719a9a0eb 100644 --- a/tools/include/uapi/linux/netdev.h +++ b/tools/include/uapi/linux/netdev.h @@ -134,6 +134,7 @@ enum { NETDEV_A_NAPI_DEFER_HARD_IRQS, NETDEV_A_NAPI_GRO_FLUSH_TIMEOUT, NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT, + NETDEV_A_NAPI_THREADED, __NETDEV_A_NAPI_MAX, NETDEV_A_NAPI_MAX = (__NETDEV_A_NAPI_MAX - 1) diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py index c9109627a741..c8ffade79a52 100755 --- a/tools/testing/selftests/net/nl_netdev.py +++ b/tools/testing/selftests/net/nl_netdev.py @@ -35,6 +35,91 @@ def napi_list_check(nf) -> None: ksft_eq(len(napis), 100, comment=f"queue count after reset queue {q} mode {i}") +def napi_set_threaded(nf) -> None: + """ + Test that verifies various cases of napi threaded + set and unset at napi and device level. + """ + with NetdevSimDev(queue_count=2) as nsimdev: + nsim = nsimdev.nsims[0] + + ip(f"link set dev {nsim.ifname} up") + + napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True) + ksft_eq(len(napis), 2) + + napi0_id = napis[0]['id'] + napi1_id = napis[1]['id'] + + # set napi threaded and verify + nf.napi_set({'id': napi0_id, 'threaded': 1}) + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], 1) + ksft_ne(napi0.get('pid'), None) + + # check it is not set for napi1 + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], 0) + ksft_eq(napi1.get('pid'), None) + + ip(f"link set dev {nsim.ifname} down") + ip(f"link set dev {nsim.ifname} up") + + # verify if napi threaded is still set + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], 1) + ksft_ne(napi0.get('pid'), None) + + # check it is still not set for napi1 + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], 0) + ksft_eq(napi1.get('pid'), None) + + # unset napi threaded and verify + nf.napi_set({'id': napi0_id, 'threaded': 0}) + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], 0) + ksft_eq(napi0.get('pid'), None) + + # set threaded at device level + system(f"echo 1 > /sys/class/net/{nsim.ifname}/threaded") + + # check napi threaded is set for both napis + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], 1) + ksft_ne(napi0.get('pid'), None) + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], 1) + ksft_ne(napi1.get('pid'), None) + + # unset threaded at device level + system(f"echo 0 > /sys/class/net/{nsim.ifname}/threaded") + + # check napi threaded is unset for both napis + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], 0) + ksft_eq(napi0.get('pid'), None) + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], 0) + ksft_eq(napi1.get('pid'), None) + + # set napi threaded for napi0 + nf.napi_set({'id': napi0_id, 'threaded': 1}) + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], 1) + ksft_ne(napi0.get('pid'), None) + + # unset threaded at device level + system(f"echo 0 > /sys/class/net/{nsim.ifname}/threaded") + + # check napi threaded is unset for both napis + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], 0) + ksft_eq(napi0.get('pid'), None) + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], 0) + ksft_eq(napi1.get('pid'), None) + def dev_set_threaded(nf) -> None: """ Test that verifies various cases of napi threaded @@ -56,8 +141,10 @@ def dev_set_threaded(nf) -> None: # check napi threaded is set for both napis napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], 1) ksft_ne(napi0.get('pid'), None) napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], 1) ksft_ne(napi1.get('pid'), None) # unset threaded @@ -65,8 +152,10 @@ def dev_set_threaded(nf) -> None: # check napi threaded is unset for both napis napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], 0) ksft_eq(napi0.get('pid'), None) napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], 0) ksft_eq(napi1.get('pid'), None) def nsim_rxq_reset_down(nf) -> None: @@ -156,7 +245,7 @@ def page_pool_check(nf) -> None: def main() -> None: nf = NetdevFamily() ksft_run([empty_check, lo_check, page_pool_check, napi_list_check, - dev_set_threaded, nsim_rxq_reset_down], + dev_set_threaded, napi_set_threaded, nsim_rxq_reset_down], args=(nf, )) ksft_exit() -- cgit v1.2.3 From 6c758062c64dfbd61862801fbde4e0702f4f3a23 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 11 Jul 2025 11:40:00 +0000 Subject: tcp: add LINUX_MIB_BEYOND_WINDOW Add a new SNMP MIB : LINUX_MIB_BEYOND_WINDOW Incremented when an incoming packet is received beyond the receiver window. nstat -az | grep TcpExtBeyondWindow Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20250711114006.480026-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- Documentation/networking/net_cachelines/snmp.rst | 1 + include/net/dropreason-core.h | 1 + include/uapi/linux/snmp.h | 1 + net/ipv4/proc.c | 1 + net/ipv4/tcp_input.c | 1 + 5 files changed, 5 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/networking/net_cachelines/snmp.rst b/Documentation/networking/net_cachelines/snmp.rst index bd44b3eebbef..bce4eb35ec48 100644 --- a/Documentation/networking/net_cachelines/snmp.rst +++ b/Documentation/networking/net_cachelines/snmp.rst @@ -36,6 +36,7 @@ unsigned_long LINUX_MIB_TIMEWAITRECYCLED unsigned_long LINUX_MIB_TIMEWAITKILLED unsigned_long LINUX_MIB_PAWSACTIVEREJECTED unsigned_long LINUX_MIB_PAWSESTABREJECTED +unsigned_long LINUX_MIB_BEYOND_WINDOW unsigned_long LINUX_MIB_TSECR_REJECTED unsigned_long LINUX_MIB_PAWS_OLD_ACK unsigned_long LINUX_MIB_PAWS_TW_REJECTED diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h index beb134d55747..229bb1826f2a 100644 --- a/include/net/dropreason-core.h +++ b/include/net/dropreason-core.h @@ -309,6 +309,7 @@ enum skb_drop_reason { /** * @SKB_DROP_REASON_TCP_INVALID_END_SEQUENCE: * Not acceptable END_SEQ field. + * Corresponds to LINUX_MIB_BEYOND_WINDOW. */ SKB_DROP_REASON_TCP_INVALID_END_SEQUENCE, /** diff --git a/include/uapi/linux/snmp.h b/include/uapi/linux/snmp.h index 1d234d7e1892..49f5640092a0 100644 --- a/include/uapi/linux/snmp.h +++ b/include/uapi/linux/snmp.h @@ -186,6 +186,7 @@ enum LINUX_MIB_TIMEWAITKILLED, /* TimeWaitKilled */ LINUX_MIB_PAWSACTIVEREJECTED, /* PAWSActiveRejected */ LINUX_MIB_PAWSESTABREJECTED, /* PAWSEstabRejected */ + LINUX_MIB_BEYOND_WINDOW, /* BeyondWindow */ LINUX_MIB_TSECRREJECTED, /* TSEcrRejected */ LINUX_MIB_PAWS_OLD_ACK, /* PAWSOldAck */ LINUX_MIB_PAWS_TW_REJECTED, /* PAWSTimewait */ diff --git a/net/ipv4/proc.c b/net/ipv4/proc.c index ea2f01584379..65b0d0ab0084 100644 --- a/net/ipv4/proc.c +++ b/net/ipv4/proc.c @@ -189,6 +189,7 @@ static const struct snmp_mib snmp4_net_list[] = { SNMP_MIB_ITEM("TWKilled", LINUX_MIB_TIMEWAITKILLED), SNMP_MIB_ITEM("PAWSActive", LINUX_MIB_PAWSACTIVEREJECTED), SNMP_MIB_ITEM("PAWSEstab", LINUX_MIB_PAWSESTABREJECTED), + SNMP_MIB_ITEM("BeyondWindow", LINUX_MIB_BEYOND_WINDOW), SNMP_MIB_ITEM("TSEcrRejected", LINUX_MIB_TSECRREJECTED), SNMP_MIB_ITEM("PAWSOldAck", LINUX_MIB_PAWS_OLD_ACK), SNMP_MIB_ITEM("PAWSTimewait", LINUX_MIB_PAWS_TW_REJECTED), diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index f0f9c78654b4..5e2d82c273e2 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -5900,6 +5900,7 @@ step1: if (!th->rst) { if (th->syn) goto syn_challenge; + NET_INC_STATS(sock_net(sk), LINUX_MIB_BEYOND_WINDOW); if (!tcp_oow_rate_limited(sock_net(sk), skb, LINUX_MIB_TCPACKSKIPPEDSEQ, &tp->last_oow_ack_time)) -- cgit v1.2.3 From c0ae03588bbb95378758fe80e7436a9b4cfc71f6 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 15 Jul 2025 17:03:21 -0700 Subject: ethtool: rss: initial RSS_SET (indirection table handling) Add initial support for RSS_SET, for now only operations on the indirection table are supported. Unlike the ioctl don't check if at least one parameter is being changed. This is how other ethtool-nl ops behave, so pick the ethtool-nl consistency vs copying ioctl behavior. There are two special cases here: 1) resetting the table to defaults; 2) support for tables of different size. For (1) I use an empty Netlink attribute (array of size 0). (2) may require some background. AFAICT a lot of modern devices allow allocating RSS tables of different sizes. mlx5 can upsize its tables, bnxt has some "table size calculation", and Intel folks asked about RSS table sizing in context of resource allocation in the past. The ethtool IOCTL API has a concept of table size, but right now the user is expected to provide a table exactly the size the device requests. Some drivers may change the table size at runtime (in response to queue count changes) but the user is not in control of this. What's not great is that all RSS contexts share the same table size. For example a device with 128 queues enabled, 16 RSS contexts 8 queues in each will likely have 256 entry tables for each of the 16 contexts, while 32 would be more than enough given each context only has 8 queues. To address this the Netlink API should avoid enforcing table size at the uAPI level, and should allow the user to express the min table size they expect. To fully solve (2) we will need more driver plumbing but at the uAPI level this patch allows the user to specify a table size smaller than what the device advertises. The device table size must be a multiple of the user requested table size. We then replicate the user-provided table to fill the full device size table. This addresses the "allow the user to express the min table size" objective, while not enforcing any fixed size. From Netlink perspective .get_rxfh_indir_size() is now de facto the "max" table size supported by the device. We may choose to support table replication in ethtool, too, when we actually plumb this thru the device APIs. Initially I was considering moving full pattern generation to the kernel (which queues to use, at which frequency and what min sequence length). I don't think this complexity would buy us much and most if not all devices have pow-2 table sizes, which simplifies the replication a lot. Reviewed-by: Gal Pressman Link: https://patch.msgid.link/20250716000331.1378807-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/ethtool.yaml | 12 ++ Documentation/networking/ethtool-netlink.rst | 26 +++- include/uapi/linux/ethtool_netlink_generated.h | 1 + net/ethtool/netlink.c | 8 + net/ethtool/netlink.h | 1 + net/ethtool/rss.c | 195 +++++++++++++++++++++++++ 6 files changed, 242 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index c38c03c624f0..1eca88a508a0 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -2643,6 +2643,18 @@ operations: attributes: - header - events + - + name: rss-set + doc: Set RSS params. + + attribute-set: rss + + do: + request: + attributes: + - header + - context + - indir - name: rss-ntf doc: | diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index 248bc3d93da9..27db7540e60e 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -239,6 +239,7 @@ Userspace to kernel: ``ETHTOOL_MSG_PHY_GET`` get Ethernet PHY information ``ETHTOOL_MSG_TSCONFIG_GET`` get hw timestamping configuration ``ETHTOOL_MSG_TSCONFIG_SET`` set hw timestamping configuration + ``ETHTOOL_MSG_RSS_SET`` set RSS settings ===================================== ================================= Kernel to userspace: @@ -292,6 +293,7 @@ Kernel to userspace: ``ETHTOOL_MSG_TSCONFIG_GET_REPLY`` hw timestamping configuration ``ETHTOOL_MSG_TSCONFIG_SET_REPLY`` new hw timestamping configuration ``ETHTOOL_MSG_PSE_NTF`` PSE events notification + ``ETHTOOL_MSG_RSS_NTF`` RSS settings notification ======================================== ================================= ``GET`` requests are sent by userspace applications to retrieve device @@ -1989,6 +1991,28 @@ hfunc. Current supported options are symmetric-xor and symmetric-or-xor. ETHTOOL_A_RSS_FLOW_HASH carries per-flow type bitmask of which header fields are included in the hash calculation. +RSS_SET +======= + +Request contents: + +===================================== ====== ============================== + ``ETHTOOL_A_RSS_HEADER`` nested request header + ``ETHTOOL_A_RSS_CONTEXT`` u32 context number + ``ETHTOOL_A_RSS_INDIR`` binary Indir table bytes +===================================== ====== ============================== + +``ETHTOOL_A_RSS_INDIR`` is the minimal RSS table the user expects. Kernel and +the device driver may replicate the table if its smaller than smallest table +size supported by the device. For example if user requests ``[0, 1]`` but the +device needs at least 8 entries - the real table in use will end up being +``[0, 1, 0, 1, 0, 1, 0, 1]``. Most devices require the table size to be power +of 2, so tables which size is not a power of 2 will likely be rejected. +Using table of size 0 will reset the indirection table to the default. + +Note that, at present, only a subset of RSS configuration can be accomplished +over Netlink. + PLCA_GET_CFG ============ @@ -2455,7 +2479,7 @@ are netlink only. ``ETHTOOL_GRXNTUPLE`` n/a ``ETHTOOL_GSSET_INFO`` ``ETHTOOL_MSG_STRSET_GET`` ``ETHTOOL_GRXFHINDIR`` ``ETHTOOL_MSG_RSS_GET`` - ``ETHTOOL_SRXFHINDIR`` n/a + ``ETHTOOL_SRXFHINDIR`` ``ETHTOOL_MSG_RSS_SET`` ``ETHTOOL_GFEATURES`` ``ETHTOOL_MSG_FEATURES_GET`` ``ETHTOOL_SFEATURES`` ``ETHTOOL_MSG_FEATURES_SET`` ``ETHTOOL_GCHANNELS`` ``ETHTOOL_MSG_CHANNELS_GET`` diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 96027e26ffba..130bdf5c3516 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -840,6 +840,7 @@ enum { ETHTOOL_MSG_PHY_GET, ETHTOOL_MSG_TSCONFIG_GET, ETHTOOL_MSG_TSCONFIG_SET, + ETHTOOL_MSG_RSS_SET, __ETHTOOL_MSG_USER_CNT, ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1) diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index b1f8999c1adc..0ae0d7a9667c 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -405,6 +405,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = { [ETHTOOL_MSG_PSE_GET] = ðnl_pse_request_ops, [ETHTOOL_MSG_PSE_SET] = ðnl_pse_request_ops, [ETHTOOL_MSG_RSS_GET] = ðnl_rss_request_ops, + [ETHTOOL_MSG_RSS_SET] = ðnl_rss_request_ops, [ETHTOOL_MSG_PLCA_GET_CFG] = ðnl_plca_cfg_request_ops, [ETHTOOL_MSG_PLCA_SET_CFG] = ðnl_plca_cfg_request_ops, [ETHTOOL_MSG_PLCA_GET_STATUS] = ðnl_plca_status_request_ops, @@ -1504,6 +1505,13 @@ static const struct genl_ops ethtool_genl_ops[] = { .policy = ethnl_tsconfig_set_policy, .maxattr = ARRAY_SIZE(ethnl_tsconfig_set_policy) - 1, }, + { + .cmd = ETHTOOL_MSG_RSS_SET, + .flags = GENL_UNS_ADMIN_PERM, + .doit = ethnl_default_set_doit, + .policy = ethnl_rss_set_policy, + .maxattr = ARRAY_SIZE(ethnl_rss_set_policy) - 1, + }, }; static const struct genl_multicast_group ethtool_nl_mcgrps[] = { diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h index 94a7eb402022..620dd1ab9b3b 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -484,6 +484,7 @@ extern const struct nla_policy ethnl_module_set_policy[ETHTOOL_A_MODULE_POWER_MO extern const struct nla_policy ethnl_pse_get_policy[ETHTOOL_A_PSE_HEADER + 1]; extern const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1]; extern const struct nla_policy ethnl_rss_get_policy[ETHTOOL_A_RSS_START_CONTEXT + 1]; +extern const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_START_CONTEXT + 1]; extern const struct nla_policy ethnl_plca_get_cfg_policy[ETHTOOL_A_PLCA_HEADER + 1]; extern const struct nla_policy ethnl_plca_set_cfg_policy[ETHTOOL_A_PLCA_MAX + 1]; extern const struct nla_policy ethnl_plca_get_status_policy[ETHTOOL_A_PLCA_HEADER + 1]; diff --git a/net/ethtool/rss.c b/net/ethtool/rss.c index 41ab9fc67652..c8db523671de 100644 --- a/net/ethtool/rss.c +++ b/net/ethtool/rss.c @@ -218,6 +218,10 @@ rss_prepare(const struct rss_req_info *request, struct net_device *dev, { rss_prepare_flow_hash(request, dev, data, info); + /* Coming from RSS_SET, driver may only have flow_hash_fields ops */ + if (!dev->ethtool_ops->get_rxfh) + return 0; + if (request->rss_context) return rss_prepare_ctx(request, dev, data, info); return rss_prepare_get(request, dev, data, info); @@ -466,6 +470,193 @@ void ethtool_rss_notify(struct net_device *dev, u32 rss_context) ethnl_notify(dev, ETHTOOL_MSG_RSS_NTF, &req_info.base); } +/* RSS_SET */ + +const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_START_CONTEXT + 1] = { + [ETHTOOL_A_RSS_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy), + [ETHTOOL_A_RSS_CONTEXT] = { .type = NLA_U32, }, + [ETHTOOL_A_RSS_INDIR] = { .type = NLA_BINARY, }, +}; + +static int +ethnl_rss_set_validate(struct ethnl_req_info *req_info, struct genl_info *info) +{ + const struct ethtool_ops *ops = req_info->dev->ethtool_ops; + struct rss_req_info *request = RSS_REQINFO(req_info); + struct nlattr **tb = info->attrs; + struct nlattr *bad_attr = NULL; + + if (request->rss_context && !ops->create_rxfh_context) + bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_CONTEXT]; + + if (bad_attr) { + NL_SET_BAD_ATTR(info->extack, bad_attr); + return -EOPNOTSUPP; + } + + return 1; +} + +static int +rss_set_prep_indir(struct net_device *dev, struct genl_info *info, + struct rss_reply_data *data, struct ethtool_rxfh_param *rxfh, + bool *reset, bool *mod) +{ + const struct ethtool_ops *ops = dev->ethtool_ops; + struct netlink_ext_ack *extack = info->extack; + struct nlattr **tb = info->attrs; + struct ethtool_rxnfc rx_rings; + size_t alloc_size; + u32 user_size; + int i, err; + + if (!tb[ETHTOOL_A_RSS_INDIR]) + return 0; + if (!data->indir_size || !ops->get_rxnfc) + return -EOPNOTSUPP; + + rx_rings.cmd = ETHTOOL_GRXRINGS; + err = ops->get_rxnfc(dev, &rx_rings, NULL); + if (err) + return err; + + if (nla_len(tb[ETHTOOL_A_RSS_INDIR]) % 4) { + NL_SET_BAD_ATTR(info->extack, tb[ETHTOOL_A_RSS_INDIR]); + return -EINVAL; + } + user_size = nla_len(tb[ETHTOOL_A_RSS_INDIR]) / 4; + if (!user_size) { + if (rxfh->rss_context) { + NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_RSS_INDIR], + "can't reset table for a context"); + return -EINVAL; + } + *reset = true; + } else if (data->indir_size % user_size) { + NL_SET_ERR_MSG_ATTR_FMT(extack, tb[ETHTOOL_A_RSS_INDIR], + "size (%d) mismatch with device indir table (%d)", + user_size, data->indir_size); + return -EINVAL; + } + + rxfh->indir_size = data->indir_size; + alloc_size = array_size(data->indir_size, sizeof(rxfh->indir[0])); + rxfh->indir = kzalloc(alloc_size, GFP_KERNEL); + if (!rxfh->indir) + return -ENOMEM; + + nla_memcpy(rxfh->indir, tb[ETHTOOL_A_RSS_INDIR], alloc_size); + for (i = 0; i < user_size; i++) { + if (rxfh->indir[i] < rx_rings.data) + continue; + + NL_SET_ERR_MSG_ATTR_FMT(extack, tb[ETHTOOL_A_RSS_INDIR], + "entry %d: queue out of range (%d)", + i, rxfh->indir[i]); + err = -EINVAL; + goto err_free; + } + + if (user_size) { + /* Replicate the user-provided table to fill the device table */ + for (i = user_size; i < data->indir_size; i++) + rxfh->indir[i] = rxfh->indir[i % user_size]; + } else { + for (i = 0; i < data->indir_size; i++) + rxfh->indir[i] = + ethtool_rxfh_indir_default(i, rx_rings.data); + } + + *mod |= memcmp(rxfh->indir, data->indir_table, data->indir_size); + + return 0; + +err_free: + kfree(rxfh->indir); + rxfh->indir = NULL; + return err; +} + +static void +rss_set_ctx_update(struct ethtool_rxfh_context *ctx, struct nlattr **tb, + struct rss_reply_data *data, struct ethtool_rxfh_param *rxfh) +{ + int i; + + if (rxfh->indir) { + for (i = 0; i < data->indir_size; i++) + ethtool_rxfh_context_indir(ctx)[i] = rxfh->indir[i]; + ctx->indir_configured = !!nla_len(tb[ETHTOOL_A_RSS_INDIR]); + } +} + +static int +ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info) +{ + struct rss_req_info *request = RSS_REQINFO(req_info); + struct ethtool_rxfh_context *ctx = NULL; + struct net_device *dev = req_info->dev; + struct ethtool_rxfh_param rxfh = {}; + bool indir_reset = false, indir_mod; + struct nlattr **tb = info->attrs; + struct rss_reply_data data = {}; + const struct ethtool_ops *ops; + bool mod = false; + int ret; + + ops = dev->ethtool_ops; + data.base.dev = dev; + + ret = rss_prepare(request, dev, &data, info); + if (ret) + return ret; + + rxfh.rss_context = request->rss_context; + + ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_reset, &mod); + if (ret) + goto exit_clean_data; + indir_mod = !!tb[ETHTOOL_A_RSS_INDIR]; + + rxfh.hfunc = ETH_RSS_HASH_NO_CHANGE; + rxfh.input_xfrm = RXH_XFRM_NO_CHANGE; + + mutex_lock(&dev->ethtool->rss_lock); + if (request->rss_context) { + ctx = xa_load(&dev->ethtool->rss_ctx, request->rss_context); + if (!ctx) { + ret = -ENOENT; + goto exit_unlock; + } + } + + if (!mod) + ret = 0; /* nothing to tell the driver */ + else if (!ops->set_rxfh) + ret = -EOPNOTSUPP; + else if (!rxfh.rss_context) + ret = ops->set_rxfh(dev, &rxfh, info->extack); + else + ret = ops->modify_rxfh_context(dev, ctx, &rxfh, info->extack); + if (ret) + goto exit_unlock; + + if (ctx) + rss_set_ctx_update(ctx, tb, &data, &rxfh); + else if (indir_reset) + dev->priv_flags &= ~IFF_RXFH_CONFIGURED; + else if (indir_mod) + dev->priv_flags |= IFF_RXFH_CONFIGURED; + +exit_unlock: + mutex_unlock(&dev->ethtool->rss_lock); + kfree(rxfh.indir); +exit_clean_data: + rss_cleanup_data(&data.base); + + return ret ?: mod; +} + const struct ethnl_request_ops ethnl_rss_request_ops = { .request_cmd = ETHTOOL_MSG_RSS_GET, .reply_cmd = ETHTOOL_MSG_RSS_GET_REPLY, @@ -478,4 +669,8 @@ const struct ethnl_request_ops ethnl_rss_request_ops = { .reply_size = rss_reply_size, .fill_reply = rss_fill_reply, .cleanup_data = rss_cleanup_data, + + .set_validate = ethnl_rss_set_validate, + .set = ethnl_rss_set, + .set_ntf_cmd = ETHTOOL_MSG_RSS_NTF, }; -- cgit v1.2.3 From 6624a0af82a6e3a4d3609264ef591a8fa3467139 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Thu, 17 Jul 2025 17:42:02 +1000 Subject: wifi: cfg80211: support configuring an S1G short beaconing BSS S1G short beacons are an optional frame type used in an S1G BSS that contain a limited set of elements. While they are optional, they are a fundamental part of S1G that enables significant power saving. Expose 2 additional netlink attributes, NL80211_ATTR_S1G_LONG_BEACON_PERIOD which denotes the number of beacon intervals between each long beacon and NL80211_ATTR_S1G_SHORT_BEACON which is a nested attribute containing the short beacon tail and head. We split them as the long beacon period cannot be updated, and is only used when initialisng the interface, whereas the short beacon data can be used to both initialise and update the templates. This follows how things such as the beacon interval and DTIM period currently operate. During the initialisation path, we ensure we have the long beacon period if the short beacon data is being passed down, whereas the update path will simply update the template if its sent down. The short beacon data is validated using the same routines for regular beacons as they support correctly parsing the short beacon format while ensuring the frame is well-formed. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20250717074205.312577-2-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 23 ++++++++++++++ include/uapi/linux/nl80211.h | 39 ++++++++++++++++++++++++ net/wireless/nl80211.c | 72 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 17f2a665dce6..44a1055a81ba 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1423,6 +1423,23 @@ struct cfg80211_unsol_bcast_probe_resp { const u8 *tmpl; }; +/** + * struct cfg80211_s1g_short_beacon - S1G short beacon data. + * + * @update: Set to true if the feature configuration should be updated. + * @short_head: Short beacon head. + * @short_tail: Short beacon tail. + * @short_head_len: Short beacon head len. + * @short_tail_len: Short beacon tail len. + */ +struct cfg80211_s1g_short_beacon { + bool update; + const u8 *short_head; + const u8 *short_tail; + size_t short_head_len; + size_t short_tail_len; +}; + /** * struct cfg80211_ap_settings - AP configuration * @@ -1463,6 +1480,8 @@ struct cfg80211_unsol_bcast_probe_resp { * @fils_discovery: FILS discovery transmission parameters * @unsol_bcast_probe_resp: Unsolicited broadcast probe response parameters * @mbssid_config: AP settings for multiple bssid + * @s1g_long_beacon_period: S1G long beacon period + * @s1g_short_beacon: S1G short beacon data */ struct cfg80211_ap_settings { struct cfg80211_chan_def chandef; @@ -1496,6 +1515,8 @@ struct cfg80211_ap_settings { struct cfg80211_fils_discovery fils_discovery; struct cfg80211_unsol_bcast_probe_resp unsol_bcast_probe_resp; struct cfg80211_mbssid_config mbssid_config; + u8 s1g_long_beacon_period; + struct cfg80211_s1g_short_beacon s1g_short_beacon; }; @@ -1507,11 +1528,13 @@ struct cfg80211_ap_settings { * @beacon: beacon data * @fils_discovery: FILS discovery transmission parameters * @unsol_bcast_probe_resp: Unsolicited broadcast probe response parameters + * @s1g_short_beacon: S1G short beacon data */ struct cfg80211_ap_update { struct cfg80211_beacon_data beacon; struct cfg80211_fils_discovery fils_discovery; struct cfg80211_unsol_bcast_probe_resp unsol_bcast_probe_resp; + struct cfg80211_s1g_short_beacon s1g_short_beacon; }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 39460334dafb..d1a14f2892d9 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -2915,6 +2915,19 @@ enum nl80211_commands { * applicable to that specific radio only. If the radio id is greater * thank the number of radios, error denoting invalid value is returned. * + * @NL80211_ATTR_S1G_LONG_BEACON_PERIOD: (u8) Integer attribute that represents + * the number of beacon intervals between each long beacon transmission + * for an S1G BSS with short beaconing enabled. This is a required + * attribute for initialising an S1G short beaconing BSS. When updating + * the short beacon data, this is not required. It has a minimum value of + * 2 (i.e 2 beacon intervals). + * + * @NL80211_ATTR_S1G_SHORT_BEACON: Nested attribute containing the short beacon + * head and tail used to set or update the short beacon templates. When + * bringing up a new interface, %NL80211_ATTR_S1G_LONG_BEACON_PERIOD is + * required alongside this attribute. Refer to + * @enum nl80211_s1g_short_beacon_attrs for the attribute definitions. + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3474,6 +3487,9 @@ enum nl80211_attrs { NL80211_ATTR_WIPHY_RADIO_INDEX, + NL80211_ATTR_S1G_LONG_BEACON_PERIOD, + NL80211_ATTR_S1G_SHORT_BEACON, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -8148,4 +8164,27 @@ enum nl80211_wiphy_radio_freq_range { NL80211_WIPHY_RADIO_FREQ_ATTR_MAX = __NL80211_WIPHY_RADIO_FREQ_ATTR_LAST - 1, }; +/** + * enum nl80211_s1g_short_beacon_attrs - S1G short beacon data + * + * @__NL80211_S1G_SHORT_BEACON_ATTR_INVALID: Invalid + * + * @NL80211_S1G_SHORT_BEACON_ATTR_HEAD: Short beacon head (binary). + * @NL80211_S1G_SHORT_BEACON_ATTR_TAIL: Short beacon tail (binary). + * + * @__NL80211_S1G_SHORT_BEACON_ATTR_LAST: Internal + * @NL80211_S1G_SHORT_BEACON_ATTR_MAX: Highest attribute + */ +enum nl80211_s1g_short_beacon_attrs { + __NL80211_S1G_SHORT_BEACON_ATTR_INVALID, + + NL80211_S1G_SHORT_BEACON_ATTR_HEAD, + NL80211_S1G_SHORT_BEACON_ATTR_TAIL, + + /* keep last */ + __NL80211_S1G_SHORT_BEACON_ATTR_LAST, + NL80211_S1G_SHORT_BEACON_ATTR_MAX = + __NL80211_S1G_SHORT_BEACON_ATTR_LAST - 1 +}; + #endif /* __LINUX_NL80211_H */ diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 20bc0f052c16..1c808b08b747 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -482,6 +482,16 @@ nl80211_sta_wme_policy[NL80211_STA_WME_MAX + 1] = { [NL80211_STA_WME_MAX_SP] = { .type = NLA_U8 }, }; +static const struct nla_policy +nl80211_s1g_short_beacon[NL80211_S1G_SHORT_BEACON_ATTR_MAX + 1] = { + [NL80211_S1G_SHORT_BEACON_ATTR_HEAD] = + NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_beacon_head, + IEEE80211_MAX_DATA_LEN), + [NL80211_S1G_SHORT_BEACON_ATTR_TAIL] = + NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_ie_attr, + IEEE80211_MAX_DATA_LEN), +}; + static const struct netlink_range_validation nl80211_punct_bitmap_range = { .min = 0, .max = 0xffff, @@ -858,6 +868,9 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_EPCS] = { .type = NLA_FLAG }, [NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS] = { .type = NLA_U16 }, [NL80211_ATTR_WIPHY_RADIO_INDEX] = { .type = NLA_U8 }, + [NL80211_ATTR_S1G_LONG_BEACON_PERIOD] = NLA_POLICY_MIN(NLA_U8, 2), + [NL80211_ATTR_S1G_SHORT_BEACON] = + NLA_POLICY_NESTED(nl80211_s1g_short_beacon), }; /* policy for the key attributes */ @@ -6202,6 +6215,41 @@ static int nl80211_validate_ap_phy_operation(struct cfg80211_ap_settings *params return 0; } +static int +nl80211_parse_s1g_short_beacon(struct cfg80211_registered_device *rdev, + struct nlattr *attrs, + struct cfg80211_s1g_short_beacon *sb) +{ + struct nlattr *tb[NL80211_S1G_SHORT_BEACON_ATTR_MAX + 1]; + int ret; + + if (!rdev->wiphy.bands[NL80211_BAND_S1GHZ]) + return -EINVAL; + + ret = nla_parse_nested(tb, NL80211_S1G_SHORT_BEACON_ATTR_MAX, attrs, + NULL, NULL); + if (ret) + return ret; + + /* Short beacon tail is optional (i.e might only include the TIM) */ + if (!tb[NL80211_S1G_SHORT_BEACON_ATTR_HEAD]) + return -EINVAL; + + sb->short_head = nla_data(tb[NL80211_S1G_SHORT_BEACON_ATTR_HEAD]); + sb->short_head_len = nla_len(tb[NL80211_S1G_SHORT_BEACON_ATTR_HEAD]); + sb->short_tail_len = 0; + + if (tb[NL80211_S1G_SHORT_BEACON_ATTR_TAIL]) { + sb->short_tail = + nla_data(tb[NL80211_S1G_SHORT_BEACON_ATTR_TAIL]); + sb->short_tail_len = + nla_len(tb[NL80211_S1G_SHORT_BEACON_ATTR_TAIL]); + } + + sb->update = true; + return 0; +} + static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; @@ -6442,6 +6490,22 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info) goto out; } + if (info->attrs[NL80211_ATTR_S1G_SHORT_BEACON]) { + if (!info->attrs[NL80211_ATTR_S1G_LONG_BEACON_PERIOD]) { + err = -EINVAL; + goto out; + } + + params->s1g_long_beacon_period = nla_get_u8( + info->attrs[NL80211_ATTR_S1G_LONG_BEACON_PERIOD]); + + err = nl80211_parse_s1g_short_beacon( + rdev, info->attrs[NL80211_ATTR_S1G_SHORT_BEACON], + ¶ms->s1g_short_beacon); + if (err) + goto out; + } + err = nl80211_calculate_ap_params(params); if (err) goto out; @@ -6550,6 +6614,14 @@ static int nl80211_set_beacon(struct sk_buff *skb, struct genl_info *info) goto out; } + attr = info->attrs[NL80211_ATTR_S1G_SHORT_BEACON]; + if (attr) { + err = nl80211_parse_s1g_short_beacon(rdev, attr, + ¶ms->s1g_short_beacon); + if (err) + goto out; + } + err = rdev_change_beacon(rdev, dev, params); out: -- cgit v1.2.3 From a166ab7816c534973745b0fe7bce3c8cefc5426f Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 17 Jul 2025 16:43:41 -0700 Subject: ethtool: rss: support creating contexts via Netlink Support creating contexts via Netlink. Setting flow hashing fields on the new context is not supported at this stage, it can be added later. An empty indirection table is not supported. This is a carry over from the IOCTL interface where empty indirection table meant delete. We can repurpose empty indirection table in Netlink but for now to avoid confusion reject it using the policy. Support letting user choose the ID for the new context. This was not possible in IOCTL since the context ID field for the create action had to be set to the ETH_RXFH_CONTEXT_ALLOC magic value. Link: https://patch.msgid.link/20250717234343.2328602-7-kuba@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/ethtool.yaml | 23 ++- Documentation/networking/ethtool-netlink.rst | 27 ++++ include/uapi/linux/ethtool_netlink_generated.h | 3 + net/ethtool/ioctl.c | 1 + net/ethtool/netlink.c | 15 ++ net/ethtool/netlink.h | 3 + net/ethtool/rss.c | 203 +++++++++++++++++++++++++ 7 files changed, 273 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index 069269edde01..25ffed5fddd5 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -2684,9 +2684,28 @@ operations: name: rss-ntf doc: | Notification for change in RSS configuration. - For additional contexts only modifications are modified, not creation - or removal of the contexts. + For additional contexts only modifications use this notification, + creation and deletion have dedicated messages. notify: rss-get + - + name: rss-create-act + doc: Create an RSS context. + attribute-set: rss + do: + request: &rss-create-attrs + attributes: + - header + - context + - hfunc + - indir + - hkey + - input-xfrm + reply: *rss-create-attrs + - + name: rss-create-ntf + doc: | + Notification for creation of an additional RSS context. + notify: rss-create-act mcast-groups: list: diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index 056832c77ffd..2646fafb8512 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -240,6 +240,7 @@ Userspace to kernel: ``ETHTOOL_MSG_TSCONFIG_GET`` get hw timestamping configuration ``ETHTOOL_MSG_TSCONFIG_SET`` set hw timestamping configuration ``ETHTOOL_MSG_RSS_SET`` set RSS settings + ``ETHTOOL_MSG_RSS_CREATE_ACT`` create an additional RSS context ===================================== ================================= Kernel to userspace: @@ -294,6 +295,8 @@ Kernel to userspace: ``ETHTOOL_MSG_TSCONFIG_SET_REPLY`` new hw timestamping configuration ``ETHTOOL_MSG_PSE_NTF`` PSE events notification ``ETHTOOL_MSG_RSS_NTF`` RSS settings notification + ``ETHTOOL_MSG_RSS_CREATE_ACT_REPLY`` create an additional RSS context + ``ETHTOOL_MSG_RSS_CREATE_NTF`` additional RSS context created ======================================== ================================= ``GET`` requests are sent by userspace applications to retrieve device @@ -2014,6 +2017,30 @@ device needs at least 8 entries - the real table in use will end up being of 2, so tables which size is not a power of 2 will likely be rejected. Using table of size 0 will reset the indirection table to the default. +RSS_CREATE_ACT +============== + +Request contents: + +===================================== ====== ============================== + ``ETHTOOL_A_RSS_HEADER`` nested request header + ``ETHTOOL_A_RSS_CONTEXT`` u32 context number + ``ETHTOOL_A_RSS_HFUNC`` u32 RSS hash func + ``ETHTOOL_A_RSS_INDIR`` binary Indir table bytes + ``ETHTOOL_A_RSS_HKEY`` binary Hash key bytes + ``ETHTOOL_A_RSS_INPUT_XFRM`` u32 RSS input data transformation +===================================== ====== ============================== + +Kernel response contents: + +===================================== ====== ============================== + ``ETHTOOL_A_RSS_HEADER`` nested request header + ``ETHTOOL_A_RSS_CONTEXT`` u32 context number +===================================== ====== ============================== + +Create an additional RSS context, if ``ETHTOOL_A_RSS_CONTEXT`` is not +specified kernel will allocate one automatically. + PLCA_GET_CFG ============ diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 130bdf5c3516..dea77abd295f 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -841,6 +841,7 @@ enum { ETHTOOL_MSG_TSCONFIG_GET, ETHTOOL_MSG_TSCONFIG_SET, ETHTOOL_MSG_RSS_SET, + ETHTOOL_MSG_RSS_CREATE_ACT, __ETHTOOL_MSG_USER_CNT, ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1) @@ -898,6 +899,8 @@ enum { ETHTOOL_MSG_TSCONFIG_SET_REPLY, ETHTOOL_MSG_PSE_NTF, ETHTOOL_MSG_RSS_NTF, + ETHTOOL_MSG_RSS_CREATE_ACT_REPLY, + ETHTOOL_MSG_RSS_CREATE_NTF, __ETHTOOL_MSG_KERNEL_CNT, ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1) diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index c53868889969..4b586b0f18e8 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -1640,6 +1640,7 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev, ntf = ETHTOOL_MSG_RSS_NTF; ret = ops->set_rxfh(dev, &rxfh_dev, extack); } else if (create) { + ntf = ETHTOOL_MSG_RSS_CREATE_NTF; ret = ops->create_rxfh_context(dev, ctx, &rxfh_dev, extack); /* Make sure driver populates defaults */ WARN_ON_ONCE(!ret && !rxfh_dev.key && ops->rxfh_per_ctx_key && diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index 0ae0d7a9667c..e9696113a96b 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -81,6 +81,12 @@ static void ethnl_sock_priv_destroy(void *priv) } } +u32 ethnl_bcast_seq_next(void) +{ + ASSERT_RTNL(); + return ++ethnl_bcast_seq; +} + int ethnl_ops_begin(struct net_device *dev) { int ret; @@ -954,6 +960,7 @@ ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = { [ETHTOOL_MSG_PLCA_NTF] = ðnl_plca_cfg_request_ops, [ETHTOOL_MSG_MM_NTF] = ðnl_mm_request_ops, [ETHTOOL_MSG_RSS_NTF] = ðnl_rss_request_ops, + [ETHTOOL_MSG_RSS_CREATE_NTF] = ðnl_rss_request_ops, }; /* default notification handler */ @@ -1061,6 +1068,7 @@ static const ethnl_notify_handler_t ethnl_notify_handlers[] = { [ETHTOOL_MSG_PLCA_NTF] = ethnl_default_notify, [ETHTOOL_MSG_MM_NTF] = ethnl_default_notify, [ETHTOOL_MSG_RSS_NTF] = ethnl_default_notify, + [ETHTOOL_MSG_RSS_CREATE_NTF] = ethnl_default_notify, }; void ethnl_notify(struct net_device *dev, unsigned int cmd, @@ -1512,6 +1520,13 @@ static const struct genl_ops ethtool_genl_ops[] = { .policy = ethnl_rss_set_policy, .maxattr = ARRAY_SIZE(ethnl_rss_set_policy) - 1, }, + { + .cmd = ETHTOOL_MSG_RSS_CREATE_ACT, + .flags = GENL_UNS_ADMIN_PERM, + .doit = ethnl_rss_create_doit, + .policy = ethnl_rss_create_policy, + .maxattr = ARRAY_SIZE(ethnl_rss_create_policy) - 1, + }, }; static const struct genl_multicast_group ethtool_nl_mcgrps[] = { diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h index ddb2fb00f929..b530bf9f85ee 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -10,6 +10,7 @@ struct ethnl_req_info; +u32 ethnl_bcast_seq_next(void); int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info, const struct nlattr *nest, struct net *net, struct netlink_ext_ack *extack, @@ -485,6 +486,7 @@ extern const struct nla_policy ethnl_pse_get_policy[ETHTOOL_A_PSE_HEADER + 1]; extern const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1]; extern const struct nla_policy ethnl_rss_get_policy[ETHTOOL_A_RSS_START_CONTEXT + 1]; extern const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_FLOW_HASH + 1]; +extern const struct nla_policy ethnl_rss_create_policy[ETHTOOL_A_RSS_INPUT_XFRM + 1]; extern const struct nla_policy ethnl_plca_get_cfg_policy[ETHTOOL_A_PLCA_HEADER + 1]; extern const struct nla_policy ethnl_plca_set_cfg_policy[ETHTOOL_A_PLCA_MAX + 1]; extern const struct nla_policy ethnl_plca_get_status_policy[ETHTOOL_A_PLCA_HEADER + 1]; @@ -507,6 +509,7 @@ int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb); int ethnl_tsinfo_start(struct netlink_callback *cb); int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct netlink_callback *cb); int ethnl_tsinfo_done(struct netlink_callback *cb); +int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info); extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN]; extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN]; diff --git a/net/ethtool/rss.c b/net/ethtool/rss.c index e5516e529b4a..be092dfa4407 100644 --- a/net/ethtool/rss.c +++ b/net/ethtool/rss.c @@ -893,3 +893,206 @@ const struct ethnl_request_ops ethnl_rss_request_ops = { .set = ethnl_rss_set, .set_ntf_cmd = ETHTOOL_MSG_RSS_NTF, }; + +/* RSS_CREATE */ + +const struct nla_policy ethnl_rss_create_policy[ETHTOOL_A_RSS_INPUT_XFRM + 1] = { + [ETHTOOL_A_RSS_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy), + [ETHTOOL_A_RSS_CONTEXT] = NLA_POLICY_MIN(NLA_U32, 1), + [ETHTOOL_A_RSS_HFUNC] = NLA_POLICY_MIN(NLA_U32, 1), + [ETHTOOL_A_RSS_INDIR] = NLA_POLICY_MIN(NLA_BINARY, 1), + [ETHTOOL_A_RSS_HKEY] = NLA_POLICY_MIN(NLA_BINARY, 1), + [ETHTOOL_A_RSS_INPUT_XFRM] = + NLA_POLICY_MAX(NLA_U32, RXH_XFRM_SYM_OR_XOR), +}; + +static int +ethnl_rss_create_validate(struct net_device *dev, struct genl_info *info) +{ + const struct ethtool_ops *ops = dev->ethtool_ops; + struct nlattr **tb = info->attrs; + struct nlattr *bad_attr = NULL; + u32 rss_context, input_xfrm; + + if (!ops->create_rxfh_context) + return -EOPNOTSUPP; + + rss_context = nla_get_u32_default(tb[ETHTOOL_A_RSS_CONTEXT], 0); + if (ops->rxfh_max_num_contexts && + ops->rxfh_max_num_contexts <= rss_context) { + NL_SET_BAD_ATTR(info->extack, tb[ETHTOOL_A_RSS_CONTEXT]); + return -ERANGE; + } + + if (!ops->rxfh_per_ctx_key) { + bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_HFUNC]; + bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_HKEY]; + bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_INPUT_XFRM]; + } + + input_xfrm = nla_get_u32_default(tb[ETHTOOL_A_RSS_INPUT_XFRM], 0); + if (input_xfrm & ~ops->supported_input_xfrm) + bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_INPUT_XFRM]; + + if (bad_attr) { + NL_SET_BAD_ATTR(info->extack, bad_attr); + return -EOPNOTSUPP; + } + + return 0; +} + +static void +ethnl_rss_create_send_ntf(struct sk_buff *rsp, struct net_device *dev) +{ + struct nlmsghdr *nlh = (void *)rsp->data; + struct genlmsghdr *genl_hdr; + + /* Convert the reply into a notification */ + nlh->nlmsg_pid = 0; + nlh->nlmsg_seq = ethnl_bcast_seq_next(); + + genl_hdr = nlmsg_data(nlh); + genl_hdr->cmd = ETHTOOL_MSG_RSS_CREATE_NTF; + + ethnl_multicast(rsp, dev); +} + +int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info) +{ + bool indir_dflt = false, mod = false, ntf_fail = false; + struct ethtool_rxfh_param rxfh = {}; + struct ethtool_rxfh_context *ctx; + struct nlattr **tb = info->attrs; + struct rss_reply_data data = {}; + const struct ethtool_ops *ops; + struct rss_req_info req = {}; + struct net_device *dev; + struct sk_buff *rsp; + void *hdr; + u32 limit; + int ret; + + rsp = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!rsp) + return -ENOMEM; + + ret = ethnl_parse_header_dev_get(&req.base, tb[ETHTOOL_A_RSS_HEADER], + genl_info_net(info), info->extack, + true); + if (ret < 0) + goto exit_free_rsp; + + dev = req.base.dev; + ops = dev->ethtool_ops; + + req.rss_context = nla_get_u32_default(tb[ETHTOOL_A_RSS_CONTEXT], 0); + + ret = ethnl_rss_create_validate(dev, info); + if (ret) + goto exit_free_dev; + + rtnl_lock(); + netdev_lock_ops(dev); + + ret = ethnl_ops_begin(dev); + if (ret < 0) + goto exit_dev_unlock; + + ret = rss_get_data_alloc(dev, &data); + if (ret) + goto exit_ops; + + ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_dflt, &mod); + if (ret) + goto exit_clean_data; + + ethnl_update_u8(&rxfh.hfunc, tb[ETHTOOL_A_RSS_HFUNC], &mod); + + ret = rss_set_prep_hkey(dev, info, &data, &rxfh, &mod); + if (ret) + goto exit_free_indir; + + rxfh.input_xfrm = RXH_XFRM_NO_CHANGE; + ethnl_update_u8(&rxfh.input_xfrm, tb[ETHTOOL_A_RSS_INPUT_XFRM], &mod); + + ctx = ethtool_rxfh_ctx_alloc(ops, data.indir_size, data.hkey_size); + if (!ctx) { + ret = -ENOMEM; + goto exit_free_hkey; + } + + mutex_lock(&dev->ethtool->rss_lock); + if (!req.rss_context) { + limit = ops->rxfh_max_num_contexts ?: U32_MAX; + ret = xa_alloc(&dev->ethtool->rss_ctx, &req.rss_context, ctx, + XA_LIMIT(1, limit - 1), GFP_KERNEL_ACCOUNT); + } else { + ret = xa_insert(&dev->ethtool->rss_ctx, + req.rss_context, ctx, GFP_KERNEL_ACCOUNT); + } + if (ret < 0) { + NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_RSS_CONTEXT], + "error allocating context ID"); + goto err_unlock_free_ctx; + } + rxfh.rss_context = req.rss_context; + + ret = ops->create_rxfh_context(dev, ctx, &rxfh, info->extack); + if (ret) + goto err_ctx_id_free; + + /* Make sure driver populates defaults */ + WARN_ON_ONCE(!rxfh.key && ops->rxfh_per_ctx_key && + !memchr_inv(ethtool_rxfh_context_key(ctx), 0, + ctx->key_size)); + + /* Store the config from rxfh to Xarray.. */ + rss_set_ctx_update(ctx, tb, &data, &rxfh); + /* .. copy from Xarray to data. */ + __rss_prepare_ctx(dev, &data, ctx); + + hdr = ethnl_unicast_put(rsp, info->snd_portid, info->snd_seq, + ETHTOOL_MSG_RSS_CREATE_ACT_REPLY); + ntf_fail = ethnl_fill_reply_header(rsp, dev, ETHTOOL_A_RSS_HEADER); + ntf_fail |= rss_fill_reply(rsp, &req.base, &data.base); + if (WARN_ON(!hdr || ntf_fail)) { + ret = -EMSGSIZE; + goto exit_unlock; + } + + genlmsg_end(rsp, hdr); + + /* Use the same skb for the response and the notification, + * genlmsg_reply() will copy the skb if it has elevated user count. + */ + skb_get(rsp); + ret = genlmsg_reply(rsp, info); + ethnl_rss_create_send_ntf(rsp, dev); + rsp = NULL; + +exit_unlock: + mutex_unlock(&dev->ethtool->rss_lock); +exit_free_hkey: + kfree(rxfh.key); +exit_free_indir: + kfree(rxfh.indir); +exit_clean_data: + rss_get_data_free(&data); +exit_ops: + ethnl_ops_complete(dev); +exit_dev_unlock: + netdev_unlock_ops(dev); + rtnl_unlock(); +exit_free_dev: + ethnl_parse_header_dev_put(&req.base); +exit_free_rsp: + nlmsg_free(rsp); + return ret; + +err_ctx_id_free: + xa_erase(&dev->ethtool->rss_ctx, req.rss_context); +err_unlock_free_ctx: + kfree(ctx); + goto exit_unlock; +} -- cgit v1.2.3 From fbe09277fa6324b50cc4eedb4d99498cf7dad897 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 17 Jul 2025 16:43:42 -0700 Subject: ethtool: rss: support removing contexts via Netlink Implement removing additional RSS contexts via Netlink. Technically it'd be possible to shoehorn the delete operation into ethnl_request_ops-compatible handler. The code ends up longer than open coded version, and I think we'll need a custom way of sending notifications at some stage (if we allow tying the context lifetime to the netlink socket, in the future). Link: https://patch.msgid.link/20250717234343.2328602-8-kuba@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/ethtool.yaml | 18 ++++ Documentation/networking/ethtool-netlink.rst | 14 ++++ include/uapi/linux/ethtool_netlink_generated.h | 2 + net/ethtool/common.c | 1 + net/ethtool/ioctl.c | 1 + net/ethtool/netlink.c | 7 ++ net/ethtool/netlink.h | 2 + net/ethtool/rss.c | 109 ++++++++++++++++++++++++- 8 files changed, 153 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index 25ffed5fddd5..1063d5d32fea 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -2706,6 +2706,24 @@ operations: doc: | Notification for creation of an additional RSS context. notify: rss-create-act + - + name: rss-delete-act + doc: Delete an RSS context. + attribute-set: rss + do: + request: + attributes: + - header + - context + - + name: rss-delete-ntf + doc: | + Notification for deletion of an additional RSS context. + attribute-set: rss + event: + attributes: + - header + - context mcast-groups: list: diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index 2646fafb8512..ab20c644af24 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -241,6 +241,7 @@ Userspace to kernel: ``ETHTOOL_MSG_TSCONFIG_SET`` set hw timestamping configuration ``ETHTOOL_MSG_RSS_SET`` set RSS settings ``ETHTOOL_MSG_RSS_CREATE_ACT`` create an additional RSS context + ``ETHTOOL_MSG_RSS_DELETE_ACT`` delete an additional RSS context ===================================== ================================= Kernel to userspace: @@ -297,6 +298,7 @@ Kernel to userspace: ``ETHTOOL_MSG_RSS_NTF`` RSS settings notification ``ETHTOOL_MSG_RSS_CREATE_ACT_REPLY`` create an additional RSS context ``ETHTOOL_MSG_RSS_CREATE_NTF`` additional RSS context created + ``ETHTOOL_MSG_RSS_DELETE_NTF`` additional RSS context deleted ======================================== ================================= ``GET`` requests are sent by userspace applications to retrieve device @@ -2041,6 +2043,18 @@ Kernel response contents: Create an additional RSS context, if ``ETHTOOL_A_RSS_CONTEXT`` is not specified kernel will allocate one automatically. +RSS_DELETE_ACT +============== + +Request contents: + +===================================== ====== ============================== + ``ETHTOOL_A_RSS_HEADER`` nested request header + ``ETHTOOL_A_RSS_CONTEXT`` u32 context number +===================================== ====== ============================== + +Delete an additional RSS context. + PLCA_GET_CFG ============ diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index dea77abd295f..e3b8813465d7 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -842,6 +842,7 @@ enum { ETHTOOL_MSG_TSCONFIG_SET, ETHTOOL_MSG_RSS_SET, ETHTOOL_MSG_RSS_CREATE_ACT, + ETHTOOL_MSG_RSS_DELETE_ACT, __ETHTOOL_MSG_USER_CNT, ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1) @@ -901,6 +902,7 @@ enum { ETHTOOL_MSG_RSS_NTF, ETHTOOL_MSG_RSS_CREATE_ACT_REPLY, ETHTOOL_MSG_RSS_CREATE_NTF, + ETHTOOL_MSG_RSS_DELETE_NTF, __ETHTOOL_MSG_KERNEL_CNT, ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1) diff --git a/net/ethtool/common.c b/net/ethtool/common.c index 2a1d40efb1fc..4f58648a27ad 100644 --- a/net/ethtool/common.c +++ b/net/ethtool/common.c @@ -1136,5 +1136,6 @@ void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id) netdev_err(dev, "device error, RSS context %d lost\n", context_id); ctx = xa_erase(&dev->ethtool->rss_ctx, context_id); kfree(ctx); + ethtool_rss_notify(dev, ETHTOOL_MSG_RSS_DELETE_NTF, context_id); } EXPORT_SYMBOL(ethtool_rxfh_context_lost); diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index 4b586b0f18e8..43a7854e784e 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -1647,6 +1647,7 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev, !memchr_inv(ethtool_rxfh_context_key(ctx), 0, ctx->key_size)); } else if (rxfh_dev.rss_delete) { + ntf = ETHTOOL_MSG_RSS_DELETE_NTF; ret = ops->remove_rxfh_context(dev, ctx, rxfh.rss_context, extack); } else { diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index e9696113a96b..2f813f25f07e 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -1527,6 +1527,13 @@ static const struct genl_ops ethtool_genl_ops[] = { .policy = ethnl_rss_create_policy, .maxattr = ARRAY_SIZE(ethnl_rss_create_policy) - 1, }, + { + .cmd = ETHTOOL_MSG_RSS_DELETE_ACT, + .flags = GENL_UNS_ADMIN_PERM, + .doit = ethnl_rss_delete_doit, + .policy = ethnl_rss_delete_policy, + .maxattr = ARRAY_SIZE(ethnl_rss_delete_policy) - 1, + }, }; static const struct genl_multicast_group ethtool_nl_mcgrps[] = { diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h index b530bf9f85ee..1d4f9ecb3d26 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -487,6 +487,7 @@ extern const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1]; extern const struct nla_policy ethnl_rss_get_policy[ETHTOOL_A_RSS_START_CONTEXT + 1]; extern const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_FLOW_HASH + 1]; extern const struct nla_policy ethnl_rss_create_policy[ETHTOOL_A_RSS_INPUT_XFRM + 1]; +extern const struct nla_policy ethnl_rss_delete_policy[ETHTOOL_A_RSS_CONTEXT + 1]; extern const struct nla_policy ethnl_plca_get_cfg_policy[ETHTOOL_A_PLCA_HEADER + 1]; extern const struct nla_policy ethnl_plca_set_cfg_policy[ETHTOOL_A_PLCA_MAX + 1]; extern const struct nla_policy ethnl_plca_get_status_policy[ETHTOOL_A_PLCA_HEADER + 1]; @@ -510,6 +511,7 @@ int ethnl_tsinfo_start(struct netlink_callback *cb); int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct netlink_callback *cb); int ethnl_tsinfo_done(struct netlink_callback *cb); int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info); +int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info); extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN]; extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN]; diff --git a/net/ethtool/rss.c b/net/ethtool/rss.c index be092dfa4407..992e98abe9dd 100644 --- a/net/ethtool/rss.c +++ b/net/ethtool/rss.c @@ -486,13 +486,49 @@ int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb) /* RSS_NTF */ +static void ethnl_rss_delete_notify(struct net_device *dev, u32 rss_context) +{ + struct sk_buff *ntf; + size_t ntf_size; + void *hdr; + + ntf_size = ethnl_reply_header_size() + + nla_total_size(sizeof(u32)); /* _RSS_CONTEXT */ + + ntf = genlmsg_new(ntf_size, GFP_KERNEL); + if (!ntf) + goto out_warn; + + hdr = ethnl_bcastmsg_put(ntf, ETHTOOL_MSG_RSS_DELETE_NTF); + if (!hdr) + goto out_free_ntf; + + if (ethnl_fill_reply_header(ntf, dev, ETHTOOL_A_RSS_HEADER) || + nla_put_u32(ntf, ETHTOOL_A_RSS_CONTEXT, rss_context)) + goto out_free_ntf; + + genlmsg_end(ntf, hdr); + if (ethnl_multicast(ntf, dev)) + goto out_warn; + + return; + +out_free_ntf: + nlmsg_free(ntf); +out_warn: + pr_warn_once("Failed to send a RSS delete notification"); +} + void ethtool_rss_notify(struct net_device *dev, u32 type, u32 rss_context) { struct rss_req_info req_info = { .rss_context = rss_context, }; - ethnl_notify(dev, type, &req_info.base); + if (type == ETHTOOL_MSG_RSS_DELETE_NTF) + ethnl_rss_delete_notify(dev, rss_context); + else + ethnl_notify(dev, type, &req_info.base); } /* RSS_SET */ @@ -1096,3 +1132,74 @@ err_unlock_free_ctx: kfree(ctx); goto exit_unlock; } + +/* RSS_DELETE */ + +const struct nla_policy ethnl_rss_delete_policy[ETHTOOL_A_RSS_CONTEXT + 1] = { + [ETHTOOL_A_RSS_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy), + [ETHTOOL_A_RSS_CONTEXT] = NLA_POLICY_MIN(NLA_U32, 1), +}; + +int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct ethtool_rxfh_context *ctx; + struct nlattr **tb = info->attrs; + struct ethnl_req_info req = {}; + const struct ethtool_ops *ops; + struct net_device *dev; + u32 rss_context; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, ETHTOOL_A_RSS_CONTEXT)) + return -EINVAL; + rss_context = nla_get_u32(tb[ETHTOOL_A_RSS_CONTEXT]); + + ret = ethnl_parse_header_dev_get(&req, tb[ETHTOOL_A_RSS_HEADER], + genl_info_net(info), info->extack, + true); + if (ret < 0) + return ret; + + dev = req.dev; + ops = dev->ethtool_ops; + + if (!ops->create_rxfh_context) + goto exit_free_dev; + + rtnl_lock(); + netdev_lock_ops(dev); + + ret = ethnl_ops_begin(dev); + if (ret < 0) + goto exit_dev_unlock; + + mutex_lock(&dev->ethtool->rss_lock); + ret = ethtool_check_rss_ctx_busy(dev, rss_context); + if (ret) + goto exit_unlock; + + ctx = xa_load(&dev->ethtool->rss_ctx, rss_context); + if (!ctx) { + ret = -ENOENT; + goto exit_unlock; + } + + ret = ops->remove_rxfh_context(dev, ctx, rss_context, info->extack); + if (ret) + goto exit_unlock; + + WARN_ON(xa_erase(&dev->ethtool->rss_ctx, rss_context) != ctx); + kfree(ctx); + + ethnl_rss_delete_notify(dev, rss_context); + +exit_unlock: + mutex_unlock(&dev->ethtool->rss_lock); + ethnl_ops_complete(dev); +exit_dev_unlock: + netdev_unlock_ops(dev); + rtnl_unlock(); +exit_free_dev: + ethnl_parse_header_dev_put(&req); + return ret; +} -- cgit v1.2.3 From 1bbdb81a98363fd5cd0c2ac16ad5346bdf814dff Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Tue, 22 Jul 2025 12:13:29 +0300 Subject: devlink: Fix excessive stack usage in rate TC bandwidth parsing The devlink_nl_rate_tc_bw_parse function uses a large stack array for devlink attributes, which triggers a warning about excessive stack usage: net/devlink/rate.c: In function 'devlink_nl_rate_tc_bw_parse': net/devlink/rate.c:382:1: error: the frame size of 1648 bytes is larger than 1536 bytes [-Werror=frame-larger-than=] Introduce a separate attribute set specifically for rate TC bandwidth parsing that only contains the two attributes actually used: index and bandwidth. This reduces the stack array from DEVLINK_ATTR_MAX entries to just 2 entries, solving the stack usage issue. Update devlink selftest to use the new 'index' and 'bw' attribute names consistent with the YAML spec. Example usage with ynl with the new spec: ./tools/net/ynl/cli.py --spec Documentation/netlink/specs/devlink.yaml \ --do rate-set --json '{ "bus-name": "pci", "dev-name": "0000:08:00.0", "port-index": 1, "rate-tc-bws": [ {"index": 0, "bw": 50}, {"index": 1, "bw": 50}, {"index": 2, "bw": 0}, {"index": 3, "bw": 0}, {"index": 4, "bw": 0}, {"index": 5, "bw": 0}, {"index": 6, "bw": 0}, {"index": 7, "bw": 0} ] }' ./tools/net/ynl/cli.py --spec Documentation/netlink/specs/devlink.yaml \ --do rate-get --json '{ "bus-name": "pci", "dev-name": "0000:08:00.0", "port-index": 1 }' output for rate-get: {'bus-name': 'pci', 'dev-name': '0000:08:00.0', 'port-index': 1, 'rate-tc-bws': [{'bw': 50, 'index': 0}, {'bw': 50, 'index': 1}, {'bw': 0, 'index': 2}, {'bw': 0, 'index': 3}, {'bw': 0, 'index': 4}, {'bw': 0, 'index': 5}, {'bw': 0, 'index': 6}, {'bw': 0, 'index': 7}], 'rate-tx-max': 0, 'rate-tx-priority': 0, 'rate-tx-share': 0, 'rate-tx-weight': 0, 'rate-type': 'leaf'} Fixes: 566e8f108fc7 ("devlink: Extend devlink rate API with traffic classes bandwidth management") Reported-by: Arnd Bergmann Closes: https://lore.kernel.org/netdev/20250708160652.1810573-1-arnd@kernel.org/ Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202507171943.W7DJcs6Y-lkp@intel.com/ Suggested-by: Jakub Kicinski Signed-off-by: Carolina Jubran Tested-by: Carolina Jubran Signed-off-by: Tariq Toukan Reviewed-by: Jiri Pirko Link: https://patch.msgid.link/1753175609-330621-1-git-send-email-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/devlink.yaml | 26 +++++++++------------- include/uapi/linux/devlink.h | 11 +++++++-- net/devlink/netlink_gen.c | 6 ++--- net/devlink/netlink_gen.h | 2 +- net/devlink/rate.c | 20 ++++++++--------- .../selftests/drivers/net/hw/devlink_rate_tc_bw.py | 16 ++++++------- 6 files changed, 42 insertions(+), 39 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index 1c4bb0cbe5f0..bb87111d5e16 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -853,18 +853,6 @@ attribute-sets: type: nest multi-attr: true nested-attributes: dl-rate-tc-bws - - - name: rate-tc-index - type: u8 - checks: - max: rate-tc-index-max - - - name: rate-tc-bw - type: u32 - doc: | - Specifies the bandwidth share assigned to the Traffic Class. - The bandwidth for the traffic class is determined - in proportion to the sum of the shares of all configured classes. - name: dl-dev-stats subset-of: devlink @@ -1271,12 +1259,20 @@ attribute-sets: type: flag - name: dl-rate-tc-bws - subset-of: devlink + name-prefix: devlink-rate-tc-attr- attributes: - - name: rate-tc-index + name: index + type: u8 + checks: + max: rate-tc-index-max - - name: rate-tc-bw + name: bw + type: u32 + doc: | + Specifies the bandwidth share assigned to the Traffic Class. + The bandwidth for the traffic class is determined + in proportion to the sum of the shares of all configured classes. operations: enum-model: directional diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index e72bcc239afd..9fcb25a0f447 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -635,8 +635,6 @@ enum devlink_attr { DEVLINK_ATTR_REGION_DIRECT, /* flag */ DEVLINK_ATTR_RATE_TC_BWS, /* nested */ - DEVLINK_ATTR_RATE_TC_INDEX, /* u8 */ - DEVLINK_ATTR_RATE_TC_BW, /* u32 */ /* Add new attributes above here, update the spec in * Documentation/netlink/specs/devlink.yaml and re-generate @@ -647,6 +645,15 @@ enum devlink_attr { DEVLINK_ATTR_MAX = __DEVLINK_ATTR_MAX - 1 }; +enum devlink_rate_tc_attr { + DEVLINK_RATE_TC_ATTR_UNSPEC, + DEVLINK_RATE_TC_ATTR_INDEX, /* u8 */ + DEVLINK_RATE_TC_ATTR_BW, /* u32 */ + + __DEVLINK_RATE_TC_ATTR_MAX, + DEVLINK_RATE_TC_ATTR_MAX = __DEVLINK_RATE_TC_ATTR_MAX - 1 +}; + /* Mapping between internal resource described by the field and system * structure */ diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c index c50436433c18..d97c326a9045 100644 --- a/net/devlink/netlink_gen.c +++ b/net/devlink/netlink_gen.c @@ -45,9 +45,9 @@ const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_ [DEVLINK_PORT_FN_ATTR_CAPS] = NLA_POLICY_BITFIELD32(15), }; -const struct nla_policy devlink_dl_rate_tc_bws_nl_policy[DEVLINK_ATTR_RATE_TC_BW + 1] = { - [DEVLINK_ATTR_RATE_TC_INDEX] = NLA_POLICY_MAX(NLA_U8, DEVLINK_RATE_TC_INDEX_MAX), - [DEVLINK_ATTR_RATE_TC_BW] = { .type = NLA_U32, }, +const struct nla_policy devlink_dl_rate_tc_bws_nl_policy[DEVLINK_RATE_TC_ATTR_BW + 1] = { + [DEVLINK_RATE_TC_ATTR_INDEX] = NLA_POLICY_MAX(NLA_U8, DEVLINK_RATE_TC_INDEX_MAX), + [DEVLINK_RATE_TC_ATTR_BW] = { .type = NLA_U32, }, }; const struct nla_policy devlink_dl_selftest_id_nl_policy[DEVLINK_ATTR_SELFTEST_ID_FLASH + 1] = { diff --git a/net/devlink/netlink_gen.h b/net/devlink/netlink_gen.h index fb733b5d4ff1..09cc6f264ccf 100644 --- a/net/devlink/netlink_gen.h +++ b/net/devlink/netlink_gen.h @@ -13,7 +13,7 @@ /* Common nested types */ extern const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_CAPS + 1]; -extern const struct nla_policy devlink_dl_rate_tc_bws_nl_policy[DEVLINK_ATTR_RATE_TC_BW + 1]; +extern const struct nla_policy devlink_dl_rate_tc_bws_nl_policy[DEVLINK_RATE_TC_ATTR_BW + 1]; extern const struct nla_policy devlink_dl_selftest_id_nl_policy[DEVLINK_ATTR_SELFTEST_ID_FLASH + 1]; /* Ops table for devlink */ diff --git a/net/devlink/rate.c b/net/devlink/rate.c index d39300a9b3d4..110b3fa8a0b1 100644 --- a/net/devlink/rate.c +++ b/net/devlink/rate.c @@ -90,8 +90,8 @@ static int devlink_rate_put_tc_bws(struct sk_buff *msg, u32 *tc_bw) if (!nla_tc_bw) return -EMSGSIZE; - if (nla_put_u8(msg, DEVLINK_ATTR_RATE_TC_INDEX, i) || - nla_put_u32(msg, DEVLINK_ATTR_RATE_TC_BW, tc_bw[i])) + if (nla_put_u8(msg, DEVLINK_RATE_TC_ATTR_INDEX, i) || + nla_put_u32(msg, DEVLINK_RATE_TC_ATTR_BW, tc_bw[i])) goto nla_put_failure; nla_nest_end(msg, nla_tc_bw); @@ -346,26 +346,26 @@ static int devlink_nl_rate_tc_bw_parse(struct nlattr *parent_nest, u32 *tc_bw, unsigned long *bitmap, struct netlink_ext_ack *extack) { - struct nlattr *tb[DEVLINK_ATTR_MAX + 1]; + struct nlattr *tb[DEVLINK_RATE_TC_ATTR_MAX + 1]; u8 tc_index; int err; - err = nla_parse_nested(tb, DEVLINK_ATTR_MAX, parent_nest, + err = nla_parse_nested(tb, DEVLINK_RATE_TC_ATTR_MAX, parent_nest, devlink_dl_rate_tc_bws_nl_policy, extack); if (err) return err; - if (!tb[DEVLINK_ATTR_RATE_TC_INDEX]) { + if (!tb[DEVLINK_RATE_TC_ATTR_INDEX]) { NL_SET_ERR_ATTR_MISS(extack, parent_nest, - DEVLINK_ATTR_RATE_TC_INDEX); + DEVLINK_RATE_TC_ATTR_INDEX); return -EINVAL; } - tc_index = nla_get_u8(tb[DEVLINK_ATTR_RATE_TC_INDEX]); + tc_index = nla_get_u8(tb[DEVLINK_RATE_TC_ATTR_INDEX]); - if (!tb[DEVLINK_ATTR_RATE_TC_BW]) { + if (!tb[DEVLINK_RATE_TC_ATTR_BW]) { NL_SET_ERR_ATTR_MISS(extack, parent_nest, - DEVLINK_ATTR_RATE_TC_BW); + DEVLINK_RATE_TC_ATTR_BW); return -EINVAL; } @@ -376,7 +376,7 @@ static int devlink_nl_rate_tc_bw_parse(struct nlattr *parent_nest, u32 *tc_bw, return -EINVAL; } - tc_bw[tc_index] = nla_get_u32(tb[DEVLINK_ATTR_RATE_TC_BW]); + tc_bw[tc_index] = nla_get_u32(tb[DEVLINK_RATE_TC_ATTR_BW]); return 0; } diff --git a/tools/testing/selftests/drivers/net/hw/devlink_rate_tc_bw.py b/tools/testing/selftests/drivers/net/hw/devlink_rate_tc_bw.py index 820d8a03becc..835c357919a8 100755 --- a/tools/testing/selftests/drivers/net/hw/devlink_rate_tc_bw.py +++ b/tools/testing/selftests/drivers/net/hw/devlink_rate_tc_bw.py @@ -208,14 +208,14 @@ def setup_devlink_rate(cfg): "port-index": port_index, "rate-tx-max": 125000000, "rate-tc-bws": [ - {"rate-tc-index": 0, "rate-tc-bw": 0}, - {"rate-tc-index": 1, "rate-tc-bw": 0}, - {"rate-tc-index": 2, "rate-tc-bw": 0}, - {"rate-tc-index": 3, "rate-tc-bw": 20}, - {"rate-tc-index": 4, "rate-tc-bw": 80}, - {"rate-tc-index": 5, "rate-tc-bw": 0}, - {"rate-tc-index": 6, "rate-tc-bw": 0}, - {"rate-tc-index": 7, "rate-tc-bw": 0}, + {"index": 0, "bw": 0}, + {"index": 1, "bw": 0}, + {"index": 2, "bw": 0}, + {"index": 3, "bw": 20}, + {"index": 4, "bw": 80}, + {"index": 5, "bw": 0}, + {"index": 6, "bw": 0}, + {"index": 7, "bw": 0}, ] }) except NlError as exc: -- cgit v1.2.3 From 320d031ad6e4d67e8e1ab08ac71efda02bc85683 Mon Sep 17 00:00:00 2001 From: Chia-Yu Chang Date: Tue, 22 Jul 2025 11:59:10 +0200 Subject: sched: Struct definition and parsing of dualpi2 qdisc DualPI2 is the reference implementation of IETF RFC9332 DualQ Coupled AQM (https://datatracker.ietf.org/doc/html/rfc9332) providing two queues called low latency (L-queue) and classic (C-queue). By default, it enqueues non-ECN and ECT(0) packets into the C-queue and ECT(1) and CE packets into the low latency queue (L-queue), as per IETF RFC9332 spec. This patch defines the dualpi2 Qdisc structure and parsing, and the following two patches include dumping and enqueue/dequeue for the DualPI2. Signed-off-by: Chia-Yu Chang Link: https://patch.msgid.link/20250722095915.24485-2-chia-yu.chang@nokia-bell-labs.com Signed-off-by: Jakub Kicinski --- include/uapi/linux/pkt_sched.h | 53 ++++ net/sched/sch_dualpi2.c | 591 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 644 insertions(+) create mode 100644 net/sched/sch_dualpi2.c (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/pkt_sched.h b/include/uapi/linux/pkt_sched.h index 3e41349f3fa2..75d685ea8368 100644 --- a/include/uapi/linux/pkt_sched.h +++ b/include/uapi/linux/pkt_sched.h @@ -1211,4 +1211,57 @@ enum { #define TCA_ETS_MAX (__TCA_ETS_MAX - 1) +/* DUALPI2 */ +enum tc_dualpi2_drop_overload { + TC_DUALPI2_DROP_OVERLOAD_OVERFLOW = 0, + TC_DUALPI2_DROP_OVERLOAD_DROP = 1, + __TCA_DUALPI2_DROP_OVERLOAD_MAX, +}; +#define TCA_DUALPI2_DROP_OVERLOAD_MAX (__TCA_DUALPI2_DROP_OVERLOAD_MAX - 1) + +enum tc_dualpi2_drop_early { + TC_DUALPI2_DROP_EARLY_DROP_DEQUEUE = 0, + TC_DUALPI2_DROP_EARLY_DROP_ENQUEUE = 1, + __TCA_DUALPI2_DROP_EARLY_MAX, +}; +#define TCA_DUALPI2_DROP_EARLY_MAX (__TCA_DUALPI2_DROP_EARLY_MAX - 1) + +enum tc_dualpi2_ecn_mask { + TC_DUALPI2_ECN_MASK_L4S_ECT = 1, + TC_DUALPI2_ECN_MASK_CLA_ECT = 2, + TC_DUALPI2_ECN_MASK_ANY_ECT = 3, + __TCA_DUALPI2_ECN_MASK_MAX, +}; +#define TCA_DUALPI2_ECN_MASK_MAX (__TCA_DUALPI2_ECN_MASK_MAX - 1) + +enum tc_dualpi2_split_gso { + TC_DUALPI2_SPLIT_GSO_NO_SPLIT_GSO = 0, + TC_DUALPI2_SPLIT_GSO_SPLIT_GSO = 1, + __TCA_DUALPI2_SPLIT_GSO_MAX, +}; +#define TCA_DUALPI2_SPLIT_GSO_MAX (__TCA_DUALPI2_SPLIT_GSO_MAX - 1) + +enum { + TCA_DUALPI2_UNSPEC, + TCA_DUALPI2_LIMIT, /* Packets */ + TCA_DUALPI2_MEMORY_LIMIT, /* Bytes */ + TCA_DUALPI2_TARGET, /* us */ + TCA_DUALPI2_TUPDATE, /* us */ + TCA_DUALPI2_ALPHA, /* Hz scaled up by 256 */ + TCA_DUALPI2_BETA, /* Hz scaled up by 256 */ + TCA_DUALPI2_STEP_THRESH_PKTS, /* Step threshold in packets */ + TCA_DUALPI2_STEP_THRESH_US, /* Step threshold in microseconds */ + TCA_DUALPI2_MIN_QLEN_STEP, /* Minimum qlen to apply STEP_THRESH */ + TCA_DUALPI2_COUPLING, /* Coupling factor between queues */ + TCA_DUALPI2_DROP_OVERLOAD, /* Whether to drop on overload */ + TCA_DUALPI2_DROP_EARLY, /* Whether to drop on enqueue */ + TCA_DUALPI2_C_PROTECTION, /* Percentage */ + TCA_DUALPI2_ECN_MASK, /* L4S queue classification mask */ + TCA_DUALPI2_SPLIT_GSO, /* Split GSO packets at enqueue */ + TCA_DUALPI2_PAD, + __TCA_DUALPI2_MAX +}; + +#define TCA_DUALPI2_MAX (__TCA_DUALPI2_MAX - 1) + #endif diff --git a/net/sched/sch_dualpi2.c b/net/sched/sch_dualpi2.c new file mode 100644 index 000000000000..c11ec66786d4 --- /dev/null +++ b/net/sched/sch_dualpi2.c @@ -0,0 +1,591 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* Copyright (C) 2024 Nokia + * + * Author: Koen De Schepper + * Author: Olga Albisser + * Author: Henrik Steen + * Author: Olivier Tilmans + * Author: Chia-Yu Chang + * + * DualPI Improved with a Square (dualpi2): + * - Supports congestion controls that comply with the Prague requirements + * in RFC9331 (e.g. TCP-Prague) + * - Supports coupled dual-queue with PI2 as defined in RFC9332 + * - Supports ECN L4S-identifier (IP.ECN==0b*1) + * + * note: Although DCTCP and BBRv3 can use shallow-threshold ECN marks, + * they do not meet the 'Prague L4S Requirements' listed in RFC 9331 + * Section 4, so they can only be used with DualPI2 in a datacenter + * context. + * + * References: + * - RFC9332: https://datatracker.ietf.org/doc/html/rfc9332 + * - De Schepper, Koen, et al. "PI 2: A linearized AQM for both classic and + * scalable TCP." in proc. ACM CoNEXT'16, 2016. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* 32b enable to support flows with windows up to ~8.6 * 1e9 packets + * i.e., twice the maximal snd_cwnd. + * MAX_PROB must be consistent with the RNG in dualpi2_roll(). + */ +#define MAX_PROB U32_MAX + +/* alpha/beta values exchanged over netlink are in units of 256ns */ +#define ALPHA_BETA_SHIFT 8 + +/* Scaled values of alpha/beta must fit in 32b to avoid overflow in later + * computations. Consequently (see and dualpi2_scale_alpha_beta()), their + * netlink-provided values can use at most 31b, i.e. be at most (2^23)-1 + * (~4MHz) as those are given in 1/256th. This enable to tune alpha/beta to + * control flows whose maximal RTTs can be in usec up to few secs. + */ +#define ALPHA_BETA_MAX ((1U << 31) - 1) + +/* Internal alpha/beta are in units of 64ns. + * This enables to use all alpha/beta values in the allowed range without loss + * of precision due to rounding when scaling them internally, e.g., + * scale_alpha_beta(1) will not round down to 0. + */ +#define ALPHA_BETA_GRANULARITY 6 + +#define ALPHA_BETA_SCALING (ALPHA_BETA_SHIFT - ALPHA_BETA_GRANULARITY) + +/* We express the weights (wc, wl) in %, i.e., wc + wl = 100 */ +#define MAX_WC 100 + +struct dualpi2_sched_data { + struct Qdisc *l_queue; /* The L4S Low latency queue (L-queue) */ + struct Qdisc *sch; /* The Classic queue (C-queue) */ + + /* Registered tc filters */ + struct tcf_proto __rcu *tcf_filters; + struct tcf_block *tcf_block; + + /* PI2 parameters */ + u64 pi2_target; /* Target delay in nanoseconds */ + u32 pi2_tupdate; /* Timer frequency in nanoseconds */ + u32 pi2_prob; /* Base PI probability */ + u32 pi2_alpha; /* Gain factor for the integral rate response */ + u32 pi2_beta; /* Gain factor for the proportional response */ + struct hrtimer pi2_timer; /* prob update timer */ + + /* Step AQM (L-queue only) parameters */ + u32 step_thresh; /* Step threshold */ + bool step_in_packets; /* Step thresh in packets (1) or time (0) */ + + /* C-queue starvation protection */ + s32 c_protection_credit; /* Credit (sign indicates which queue) */ + s32 c_protection_init; /* Reset value of the credit */ + u8 c_protection_wc; /* C-queue weight (between 0 and MAX_WC) */ + u8 c_protection_wl; /* L-queue weight (MAX_WC - wc) */ + + /* General dualQ parameters */ + u32 memory_limit; /* Memory limit of both queues */ + u8 coupling_factor;/* Coupling factor (k) between both queues */ + u8 ecn_mask; /* Mask to match packets into L-queue */ + u32 min_qlen_step; /* Minimum queue length to apply step thresh */ + bool drop_early; /* Drop at enqueue (1) instead of dequeue (0) */ + bool drop_overload; /* Drop (1) on overload, or overflow (0) */ + bool split_gso; /* Split aggregated skb (1) or leave as is (0) */ + + /* Statistics */ + u64 c_head_ts; /* Enqueue timestamp of the C-queue head */ + u64 l_head_ts; /* Enqueue timestamp of the L-queue head */ + u64 last_qdelay; /* Q delay val at the last probability update */ + u32 packets_in_c; /* Enqueue packet counter of the C-queue */ + u32 packets_in_l; /* Enqueue packet counter of the L-queue */ + u32 maxq; /* Maximum queue size of the C-queue */ + u32 ecn_mark; /* ECN mark pkt counter due to PI probability */ + u32 step_marks; /* ECN mark pkt counter due to step AQM */ + u32 memory_used; /* Memory used of both queues */ + u32 max_memory_used;/* Maximum used memory */ +}; + +static u32 dualpi2_scale_alpha_beta(u32 param) +{ + u64 tmp = ((u64)param * MAX_PROB >> ALPHA_BETA_SCALING); + + do_div(tmp, NSEC_PER_SEC); + return tmp; +} + +static ktime_t next_pi2_timeout(struct dualpi2_sched_data *q) +{ + return ktime_add_ns(ktime_get_ns(), q->pi2_tupdate); +} + +static void dualpi2_reset_c_protection(struct dualpi2_sched_data *q) +{ + q->c_protection_credit = q->c_protection_init; +} + +/* This computes the initial credit value and WRR weight for the L queue (wl) + * from the weight of the C queue (wc). + * If wl > wc, the scheduler will start with the L queue when reset. + */ +static void dualpi2_calculate_c_protection(struct Qdisc *sch, + struct dualpi2_sched_data *q, u32 wc) +{ + q->c_protection_wc = wc; + q->c_protection_wl = MAX_WC - wc; + q->c_protection_init = (s32)psched_mtu(qdisc_dev(sch)) * + ((int)q->c_protection_wc - (int)q->c_protection_wl); + dualpi2_reset_c_protection(q); +} + +static s64 __scale_delta(u64 diff) +{ + do_div(diff, 1 << ALPHA_BETA_GRANULARITY); + return diff; +} + +static void get_queue_delays(struct dualpi2_sched_data *q, u64 *qdelay_c, + u64 *qdelay_l) +{ + u64 now, qc, ql; + + now = ktime_get_ns(); + qc = q->c_head_ts; + ql = q->l_head_ts; + + *qdelay_c = qc ? now - qc : 0; + *qdelay_l = ql ? now - ql : 0; +} + +static u32 calculate_probability(struct Qdisc *sch) +{ + struct dualpi2_sched_data *q = qdisc_priv(sch); + u32 new_prob; + u64 qdelay_c; + u64 qdelay_l; + u64 qdelay; + s64 delta; + + get_queue_delays(q, &qdelay_c, &qdelay_l); + qdelay = max(qdelay_l, qdelay_c); + + /* Alpha and beta take at most 32b, i.e, the delay difference would + * overflow for queuing delay differences > ~4.2sec. + */ + delta = ((s64)qdelay - (s64)q->pi2_target) * q->pi2_alpha; + delta += ((s64)qdelay - (s64)q->last_qdelay) * q->pi2_beta; + q->last_qdelay = qdelay; + + /* Bound new_prob between 0 and MAX_PROB */ + if (delta > 0) { + new_prob = __scale_delta(delta) + q->pi2_prob; + if (new_prob < q->pi2_prob) + new_prob = MAX_PROB; + } else { + new_prob = q->pi2_prob - __scale_delta(~delta + 1); + if (new_prob > q->pi2_prob) + new_prob = 0; + } + + /* If we do not drop on overload, ensure we cap the L4S probability to + * 100% to keep window fairness when overflowing. + */ + if (!q->drop_overload) + return min_t(u32, new_prob, MAX_PROB / q->coupling_factor); + return new_prob; +} + +static u32 get_memory_limit(struct Qdisc *sch, u32 limit) +{ + /* Apply rule of thumb, i.e., doubling the packet length, + * to further include per packet overhead in memory_limit. + */ + u64 memlim = mul_u32_u32(limit, 2 * psched_mtu(qdisc_dev(sch))); + + if (upper_32_bits(memlim)) + return U32_MAX; + else + return lower_32_bits(memlim); +} + +static u32 convert_us_to_nsec(u32 us) +{ + u64 ns = mul_u32_u32(us, NSEC_PER_USEC); + + if (upper_32_bits(ns)) + return U32_MAX; + + return lower_32_bits(ns); +} + +static enum hrtimer_restart dualpi2_timer(struct hrtimer *timer) +{ + struct dualpi2_sched_data *q = timer_container_of(q, timer, pi2_timer); + struct Qdisc *sch = q->sch; + spinlock_t *root_lock; /* to lock qdisc for probability calculations */ + + rcu_read_lock(); + root_lock = qdisc_lock(qdisc_root_sleeping(sch)); + spin_lock(root_lock); + + q->pi2_prob = calculate_probability(sch); + hrtimer_set_expires(&q->pi2_timer, next_pi2_timeout(q)); + + spin_unlock(root_lock); + rcu_read_unlock(); + return HRTIMER_RESTART; +} + +static struct netlink_range_validation dualpi2_alpha_beta_range = { + .min = 1, + .max = ALPHA_BETA_MAX, +}; + +static const struct nla_policy dualpi2_policy[TCA_DUALPI2_MAX + 1] = { + [TCA_DUALPI2_LIMIT] = NLA_POLICY_MIN(NLA_U32, 1), + [TCA_DUALPI2_MEMORY_LIMIT] = NLA_POLICY_MIN(NLA_U32, 1), + [TCA_DUALPI2_TARGET] = { .type = NLA_U32 }, + [TCA_DUALPI2_TUPDATE] = NLA_POLICY_MIN(NLA_U32, 1), + [TCA_DUALPI2_ALPHA] = + NLA_POLICY_FULL_RANGE(NLA_U32, &dualpi2_alpha_beta_range), + [TCA_DUALPI2_BETA] = + NLA_POLICY_FULL_RANGE(NLA_U32, &dualpi2_alpha_beta_range), + [TCA_DUALPI2_STEP_THRESH_PKTS] = { .type = NLA_U32 }, + [TCA_DUALPI2_STEP_THRESH_US] = { .type = NLA_U32 }, + [TCA_DUALPI2_MIN_QLEN_STEP] = { .type = NLA_U32 }, + [TCA_DUALPI2_COUPLING] = NLA_POLICY_MIN(NLA_U8, 1), + [TCA_DUALPI2_DROP_OVERLOAD] = + NLA_POLICY_MAX(NLA_U8, TCA_DUALPI2_DROP_OVERLOAD_MAX), + [TCA_DUALPI2_DROP_EARLY] = + NLA_POLICY_MAX(NLA_U8, TCA_DUALPI2_DROP_EARLY_MAX), + [TCA_DUALPI2_C_PROTECTION] = + NLA_POLICY_RANGE(NLA_U8, 0, MAX_WC), + [TCA_DUALPI2_ECN_MASK] = + NLA_POLICY_RANGE(NLA_U8, TC_DUALPI2_ECN_MASK_L4S_ECT, + TCA_DUALPI2_ECN_MASK_MAX), + [TCA_DUALPI2_SPLIT_GSO] = + NLA_POLICY_MAX(NLA_U8, TCA_DUALPI2_SPLIT_GSO_MAX), +}; + +static int dualpi2_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) +{ + struct nlattr *tb[TCA_DUALPI2_MAX + 1]; + struct dualpi2_sched_data *q; + int old_backlog; + int old_qlen; + int err; + + if (!opt || !nla_len(opt)) { + NL_SET_ERR_MSG_MOD(extack, "Dualpi2 options are required"); + return -EINVAL; + } + err = nla_parse_nested(tb, TCA_DUALPI2_MAX, opt, dualpi2_policy, + extack); + if (err < 0) + return err; + if (tb[TCA_DUALPI2_STEP_THRESH_PKTS] && tb[TCA_DUALPI2_STEP_THRESH_US]) { + NL_SET_ERR_MSG_MOD(extack, "multiple step thresh attributes"); + return -EINVAL; + } + + q = qdisc_priv(sch); + sch_tree_lock(sch); + + if (tb[TCA_DUALPI2_LIMIT]) { + u32 limit = nla_get_u32(tb[TCA_DUALPI2_LIMIT]); + + sch->limit = limit; + q->memory_limit = get_memory_limit(sch, limit); + } + + if (tb[TCA_DUALPI2_MEMORY_LIMIT]) + q->memory_limit = nla_get_u32(tb[TCA_DUALPI2_MEMORY_LIMIT]); + + if (tb[TCA_DUALPI2_TARGET]) { + u64 target = nla_get_u32(tb[TCA_DUALPI2_TARGET]); + + q->pi2_target = target * NSEC_PER_USEC; + } + + if (tb[TCA_DUALPI2_TUPDATE]) { + u64 tupdate = nla_get_u32(tb[TCA_DUALPI2_TUPDATE]); + + q->pi2_tupdate = convert_us_to_nsec(tupdate); + } + + if (tb[TCA_DUALPI2_ALPHA]) { + u32 alpha = nla_get_u32(tb[TCA_DUALPI2_ALPHA]); + + q->pi2_alpha = dualpi2_scale_alpha_beta(alpha); + } + + if (tb[TCA_DUALPI2_BETA]) { + u32 beta = nla_get_u32(tb[TCA_DUALPI2_BETA]); + + q->pi2_beta = dualpi2_scale_alpha_beta(beta); + } + + if (tb[TCA_DUALPI2_STEP_THRESH_PKTS]) { + u32 step_th = nla_get_u32(tb[TCA_DUALPI2_STEP_THRESH_PKTS]); + + q->step_in_packets = true; + q->step_thresh = step_th; + } else if (tb[TCA_DUALPI2_STEP_THRESH_US]) { + u32 step_th = nla_get_u32(tb[TCA_DUALPI2_STEP_THRESH_US]); + + q->step_in_packets = false; + q->step_thresh = convert_us_to_nsec(step_th); + } + + if (tb[TCA_DUALPI2_MIN_QLEN_STEP]) + q->min_qlen_step = nla_get_u32(tb[TCA_DUALPI2_MIN_QLEN_STEP]); + + if (tb[TCA_DUALPI2_COUPLING]) { + u8 coupling = nla_get_u8(tb[TCA_DUALPI2_COUPLING]); + + q->coupling_factor = coupling; + } + + if (tb[TCA_DUALPI2_DROP_OVERLOAD]) { + u8 drop_overload = nla_get_u8(tb[TCA_DUALPI2_DROP_OVERLOAD]); + + q->drop_overload = (bool)drop_overload; + } + + if (tb[TCA_DUALPI2_DROP_EARLY]) { + u8 drop_early = nla_get_u8(tb[TCA_DUALPI2_DROP_EARLY]); + + q->drop_early = (bool)drop_early; + } + + if (tb[TCA_DUALPI2_C_PROTECTION]) { + u8 wc = nla_get_u8(tb[TCA_DUALPI2_C_PROTECTION]); + + dualpi2_calculate_c_protection(sch, q, wc); + } + + if (tb[TCA_DUALPI2_ECN_MASK]) { + u8 ecn_mask = nla_get_u8(tb[TCA_DUALPI2_ECN_MASK]); + + q->ecn_mask = ecn_mask; + } + + if (tb[TCA_DUALPI2_SPLIT_GSO]) { + u8 split_gso = nla_get_u8(tb[TCA_DUALPI2_SPLIT_GSO]); + + q->split_gso = (bool)split_gso; + } + + old_qlen = qdisc_qlen(sch); + old_backlog = sch->qstats.backlog; + while (qdisc_qlen(sch) > sch->limit || + q->memory_used > q->memory_limit) { + struct sk_buff *skb = qdisc_dequeue_internal(sch, true); + + q->memory_used -= skb->truesize; + qdisc_qstats_backlog_dec(sch, skb); + rtnl_qdisc_drop(skb, sch); + } + qdisc_tree_reduce_backlog(sch, old_qlen - qdisc_qlen(sch), + old_backlog - sch->qstats.backlog); + + sch_tree_unlock(sch); + return 0; +} + +/* Default alpha/beta values give a 10dB stability margin with max_rtt=100ms. */ +static void dualpi2_reset_default(struct Qdisc *sch) +{ + struct dualpi2_sched_data *q = qdisc_priv(sch); + + q->sch->limit = 10000; /* Max 125ms at 1Gbps */ + q->memory_limit = get_memory_limit(sch, q->sch->limit); + + q->pi2_target = 15 * NSEC_PER_MSEC; + q->pi2_tupdate = 16 * NSEC_PER_MSEC; + q->pi2_alpha = dualpi2_scale_alpha_beta(41); /* ~0.16 Hz * 256 */ + q->pi2_beta = dualpi2_scale_alpha_beta(819); /* ~3.20 Hz * 256 */ + + q->step_thresh = 1 * NSEC_PER_MSEC; + q->step_in_packets = false; + + dualpi2_calculate_c_protection(q->sch, q, 10); /* wc=10%, wl=90% */ + + q->ecn_mask = TC_DUALPI2_ECN_MASK_L4S_ECT; /* INET_ECN_ECT_1 */ + q->min_qlen_step = 0; /* Always apply step mark in L-queue */ + q->coupling_factor = 2; /* window fairness for equal RTTs */ + q->drop_overload = TC_DUALPI2_DROP_OVERLOAD_DROP; /* Drop overload */ + q->drop_early = TC_DUALPI2_DROP_EARLY_DROP_DEQUEUE; /* Drop dequeue */ + q->split_gso = TC_DUALPI2_SPLIT_GSO_SPLIT_GSO; /* Split GSO */ +} + +static int dualpi2_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) +{ + struct dualpi2_sched_data *q = qdisc_priv(sch); + int err; + + q->l_queue = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, + TC_H_MAKE(sch->handle, 1), extack); + if (!q->l_queue) + return -ENOMEM; + + err = tcf_block_get(&q->tcf_block, &q->tcf_filters, sch, extack); + if (err) + return err; + + q->sch = sch; + dualpi2_reset_default(sch); + hrtimer_setup(&q->pi2_timer, dualpi2_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED); + + if (opt && nla_len(opt)) { + err = dualpi2_change(sch, opt, extack); + + if (err) + return err; + } + + hrtimer_start(&q->pi2_timer, next_pi2_timeout(q), + HRTIMER_MODE_ABS_PINNED); + return 0; +} + +/* Reset both L-queue and C-queue, internal packet counters, PI probability, + * C-queue protection credit, and timestamps, while preserving current + * configuration of DUALPI2. + */ +static void dualpi2_reset(struct Qdisc *sch) +{ + struct dualpi2_sched_data *q = qdisc_priv(sch); + + qdisc_reset_queue(sch); + qdisc_reset_queue(q->l_queue); + q->c_head_ts = 0; + q->l_head_ts = 0; + q->pi2_prob = 0; + q->packets_in_c = 0; + q->packets_in_l = 0; + q->maxq = 0; + q->ecn_mark = 0; + q->step_marks = 0; + q->memory_used = 0; + q->max_memory_used = 0; + dualpi2_reset_c_protection(q); +} + +static void dualpi2_destroy(struct Qdisc *sch) +{ + struct dualpi2_sched_data *q = qdisc_priv(sch); + + q->pi2_tupdate = 0; + hrtimer_cancel(&q->pi2_timer); + if (q->l_queue) + qdisc_put(q->l_queue); + tcf_block_put(q->tcf_block); +} + +static struct Qdisc *dualpi2_leaf(struct Qdisc *sch, unsigned long arg) +{ + return NULL; +} + +static unsigned long dualpi2_find(struct Qdisc *sch, u32 classid) +{ + return 0; +} + +static unsigned long dualpi2_bind(struct Qdisc *sch, unsigned long parent, + u32 classid) +{ + return 0; +} + +static void dualpi2_unbind(struct Qdisc *q, unsigned long cl) +{ +} + +static struct tcf_block *dualpi2_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) +{ + struct dualpi2_sched_data *q = qdisc_priv(sch); + + if (cl) + return NULL; + return q->tcf_block; +} + +static void dualpi2_walk(struct Qdisc *sch, struct qdisc_walker *arg) +{ + unsigned int i; + + if (arg->stop) + return; + + /* We statically define only 2 queues */ + for (i = 0; i < 2; i++) { + if (arg->count < arg->skip) { + arg->count++; + continue; + } + if (arg->fn(sch, i + 1, arg) < 0) { + arg->stop = 1; + break; + } + arg->count++; + } +} + +/* Minimal class support to handle tc filters */ +static const struct Qdisc_class_ops dualpi2_class_ops = { + .leaf = dualpi2_leaf, + .find = dualpi2_find, + .tcf_block = dualpi2_tcf_block, + .bind_tcf = dualpi2_bind, + .unbind_tcf = dualpi2_unbind, + .walk = dualpi2_walk, +}; + +static struct Qdisc_ops dualpi2_qdisc_ops __read_mostly = { + .id = "dualpi2", + .cl_ops = &dualpi2_class_ops, + .priv_size = sizeof(struct dualpi2_sched_data), + .peek = qdisc_peek_dequeued, + .init = dualpi2_init, + .destroy = dualpi2_destroy, + .reset = dualpi2_reset, + .change = dualpi2_change, + .owner = THIS_MODULE, +}; + +static int __init dualpi2_module_init(void) +{ + return register_qdisc(&dualpi2_qdisc_ops); +} + +static void __exit dualpi2_module_exit(void) +{ + unregister_qdisc(&dualpi2_qdisc_ops); +} + +module_init(dualpi2_module_init); +module_exit(dualpi2_module_exit); + +MODULE_DESCRIPTION("Dual Queue with Proportional Integral controller Improved with a Square (dualpi2) scheduler"); +MODULE_AUTHOR("Koen De Schepper "); +MODULE_AUTHOR("Chia-Yu Chang "); +MODULE_AUTHOR("Olga Albisser "); +MODULE_AUTHOR("Henrik Steen "); +MODULE_AUTHOR("Olivier Tilmans "); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_VERSION("1.0"); -- cgit v1.2.3 From d4de8bffbef4a7e4ad14b9fd2ff8e2d0e06b3fa5 Mon Sep 17 00:00:00 2001 From: Chia-Yu Chang Date: Tue, 22 Jul 2025 11:59:11 +0200 Subject: sched: Dump configuration and statistics of dualpi2 qdisc The configuration and statistics dump of the DualPI2 Qdisc provides information related to both queues, such as packet numbers and queuing delays in the L-queue and C-queue, as well as general information such as probability value, WRR credits, memory usage, packet marking counters, max queue size, etc. The following patch includes enqueue/dequeue for DualPI2. Signed-off-by: Chia-Yu Chang Link: https://patch.msgid.link/20250722095915.24485-3-chia-yu.chang@nokia-bell-labs.com Signed-off-by: Jakub Kicinski --- include/uapi/linux/pkt_sched.h | 15 ++++ net/sched/sch_dualpi2.c | 154 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 152 insertions(+), 17 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/pkt_sched.h b/include/uapi/linux/pkt_sched.h index 75d685ea8368..c2da76e78bad 100644 --- a/include/uapi/linux/pkt_sched.h +++ b/include/uapi/linux/pkt_sched.h @@ -1264,4 +1264,19 @@ enum { #define TCA_DUALPI2_MAX (__TCA_DUALPI2_MAX - 1) +struct tc_dualpi2_xstats { + __u32 prob; /* current probability */ + __u32 delay_c; /* current delay in C queue */ + __u32 delay_l; /* current delay in L queue */ + __u32 packets_in_c; /* number of packets enqueued in C queue */ + __u32 packets_in_l; /* number of packets enqueued in L queue */ + __u32 maxq; /* maximum queue size */ + __u32 ecn_mark; /* packets marked with ecn*/ + __u32 step_marks; /* ECN marks due to the step AQM */ + __s32 credit; /* current c_protection credit */ + __u32 memory_used; /* Memory used by both queues */ + __u32 max_memory_used; /* Maximum used memory */ + __u32 memory_limit; /* Memory limit of both queues */ +}; + #endif diff --git a/net/sched/sch_dualpi2.c b/net/sched/sch_dualpi2.c index c11ec66786d4..0a96d57c40d1 100644 --- a/net/sched/sch_dualpi2.c +++ b/net/sched/sch_dualpi2.c @@ -123,6 +123,14 @@ static u32 dualpi2_scale_alpha_beta(u32 param) return tmp; } +static u32 dualpi2_unscale_alpha_beta(u32 param) +{ + u64 tmp = ((u64)param * NSEC_PER_SEC << ALPHA_BETA_SCALING); + + do_div(tmp, MAX_PROB); + return tmp; +} + static ktime_t next_pi2_timeout(struct dualpi2_sched_data *q) { return ktime_add_ns(ktime_get_ns(), q->pi2_tupdate); @@ -227,6 +235,15 @@ static u32 convert_us_to_nsec(u32 us) return lower_32_bits(ns); } +static u32 convert_ns_to_usec(u64 ns) +{ + do_div(ns, NSEC_PER_USEC); + if (upper_32_bits(ns)) + return U32_MAX; + + return lower_32_bits(ns); +} + static enum hrtimer_restart dualpi2_timer(struct hrtimer *timer) { struct dualpi2_sched_data *q = timer_container_of(q, timer, pi2_timer); @@ -304,68 +321,70 @@ static int dualpi2_change(struct Qdisc *sch, struct nlattr *opt, if (tb[TCA_DUALPI2_LIMIT]) { u32 limit = nla_get_u32(tb[TCA_DUALPI2_LIMIT]); - sch->limit = limit; - q->memory_limit = get_memory_limit(sch, limit); + WRITE_ONCE(sch->limit, limit); + WRITE_ONCE(q->memory_limit, get_memory_limit(sch, limit)); } if (tb[TCA_DUALPI2_MEMORY_LIMIT]) - q->memory_limit = nla_get_u32(tb[TCA_DUALPI2_MEMORY_LIMIT]); + WRITE_ONCE(q->memory_limit, + nla_get_u32(tb[TCA_DUALPI2_MEMORY_LIMIT])); if (tb[TCA_DUALPI2_TARGET]) { u64 target = nla_get_u32(tb[TCA_DUALPI2_TARGET]); - q->pi2_target = target * NSEC_PER_USEC; + WRITE_ONCE(q->pi2_target, target * NSEC_PER_USEC); } if (tb[TCA_DUALPI2_TUPDATE]) { u64 tupdate = nla_get_u32(tb[TCA_DUALPI2_TUPDATE]); - q->pi2_tupdate = convert_us_to_nsec(tupdate); + WRITE_ONCE(q->pi2_tupdate, convert_us_to_nsec(tupdate)); } if (tb[TCA_DUALPI2_ALPHA]) { u32 alpha = nla_get_u32(tb[TCA_DUALPI2_ALPHA]); - q->pi2_alpha = dualpi2_scale_alpha_beta(alpha); + WRITE_ONCE(q->pi2_alpha, dualpi2_scale_alpha_beta(alpha)); } if (tb[TCA_DUALPI2_BETA]) { u32 beta = nla_get_u32(tb[TCA_DUALPI2_BETA]); - q->pi2_beta = dualpi2_scale_alpha_beta(beta); + WRITE_ONCE(q->pi2_beta, dualpi2_scale_alpha_beta(beta)); } if (tb[TCA_DUALPI2_STEP_THRESH_PKTS]) { u32 step_th = nla_get_u32(tb[TCA_DUALPI2_STEP_THRESH_PKTS]); - q->step_in_packets = true; - q->step_thresh = step_th; + WRITE_ONCE(q->step_in_packets, true); + WRITE_ONCE(q->step_thresh, step_th); } else if (tb[TCA_DUALPI2_STEP_THRESH_US]) { u32 step_th = nla_get_u32(tb[TCA_DUALPI2_STEP_THRESH_US]); - q->step_in_packets = false; - q->step_thresh = convert_us_to_nsec(step_th); + WRITE_ONCE(q->step_in_packets, false); + WRITE_ONCE(q->step_thresh, convert_us_to_nsec(step_th)); } if (tb[TCA_DUALPI2_MIN_QLEN_STEP]) - q->min_qlen_step = nla_get_u32(tb[TCA_DUALPI2_MIN_QLEN_STEP]); + WRITE_ONCE(q->min_qlen_step, + nla_get_u32(tb[TCA_DUALPI2_MIN_QLEN_STEP])); if (tb[TCA_DUALPI2_COUPLING]) { u8 coupling = nla_get_u8(tb[TCA_DUALPI2_COUPLING]); - q->coupling_factor = coupling; + WRITE_ONCE(q->coupling_factor, coupling); } if (tb[TCA_DUALPI2_DROP_OVERLOAD]) { u8 drop_overload = nla_get_u8(tb[TCA_DUALPI2_DROP_OVERLOAD]); - q->drop_overload = (bool)drop_overload; + WRITE_ONCE(q->drop_overload, (bool)drop_overload); } if (tb[TCA_DUALPI2_DROP_EARLY]) { u8 drop_early = nla_get_u8(tb[TCA_DUALPI2_DROP_EARLY]); - q->drop_early = (bool)drop_early; + WRITE_ONCE(q->drop_early, (bool)drop_early); } if (tb[TCA_DUALPI2_C_PROTECTION]) { @@ -377,13 +396,13 @@ static int dualpi2_change(struct Qdisc *sch, struct nlattr *opt, if (tb[TCA_DUALPI2_ECN_MASK]) { u8 ecn_mask = nla_get_u8(tb[TCA_DUALPI2_ECN_MASK]); - q->ecn_mask = ecn_mask; + WRITE_ONCE(q->ecn_mask, ecn_mask); } if (tb[TCA_DUALPI2_SPLIT_GSO]) { u8 split_gso = nla_get_u8(tb[TCA_DUALPI2_SPLIT_GSO]); - q->split_gso = (bool)split_gso; + WRITE_ONCE(q->split_gso, (bool)split_gso); } old_qlen = qdisc_qlen(sch); @@ -460,6 +479,105 @@ static int dualpi2_init(struct Qdisc *sch, struct nlattr *opt, return 0; } +static int dualpi2_dump(struct Qdisc *sch, struct sk_buff *skb) +{ + struct dualpi2_sched_data *q = qdisc_priv(sch); + struct nlattr *opts; + bool step_in_pkts; + u32 step_th; + + step_in_pkts = READ_ONCE(q->step_in_packets); + step_th = READ_ONCE(q->step_thresh); + + opts = nla_nest_start_noflag(skb, TCA_OPTIONS); + if (!opts) + goto nla_put_failure; + + if (step_in_pkts && + (nla_put_u32(skb, TCA_DUALPI2_LIMIT, READ_ONCE(sch->limit)) || + nla_put_u32(skb, TCA_DUALPI2_MEMORY_LIMIT, + READ_ONCE(q->memory_limit)) || + nla_put_u32(skb, TCA_DUALPI2_TARGET, + convert_ns_to_usec(READ_ONCE(q->pi2_target))) || + nla_put_u32(skb, TCA_DUALPI2_TUPDATE, + convert_ns_to_usec(READ_ONCE(q->pi2_tupdate))) || + nla_put_u32(skb, TCA_DUALPI2_ALPHA, + dualpi2_unscale_alpha_beta(READ_ONCE(q->pi2_alpha))) || + nla_put_u32(skb, TCA_DUALPI2_BETA, + dualpi2_unscale_alpha_beta(READ_ONCE(q->pi2_beta))) || + nla_put_u32(skb, TCA_DUALPI2_STEP_THRESH_PKTS, step_th) || + nla_put_u32(skb, TCA_DUALPI2_MIN_QLEN_STEP, + READ_ONCE(q->min_qlen_step)) || + nla_put_u8(skb, TCA_DUALPI2_COUPLING, + READ_ONCE(q->coupling_factor)) || + nla_put_u8(skb, TCA_DUALPI2_DROP_OVERLOAD, + READ_ONCE(q->drop_overload)) || + nla_put_u8(skb, TCA_DUALPI2_DROP_EARLY, + READ_ONCE(q->drop_early)) || + nla_put_u8(skb, TCA_DUALPI2_C_PROTECTION, + READ_ONCE(q->c_protection_wc)) || + nla_put_u8(skb, TCA_DUALPI2_ECN_MASK, READ_ONCE(q->ecn_mask)) || + nla_put_u8(skb, TCA_DUALPI2_SPLIT_GSO, READ_ONCE(q->split_gso)))) + goto nla_put_failure; + + if (!step_in_pkts && + (nla_put_u32(skb, TCA_DUALPI2_LIMIT, READ_ONCE(sch->limit)) || + nla_put_u32(skb, TCA_DUALPI2_MEMORY_LIMIT, + READ_ONCE(q->memory_limit)) || + nla_put_u32(skb, TCA_DUALPI2_TARGET, + convert_ns_to_usec(READ_ONCE(q->pi2_target))) || + nla_put_u32(skb, TCA_DUALPI2_TUPDATE, + convert_ns_to_usec(READ_ONCE(q->pi2_tupdate))) || + nla_put_u32(skb, TCA_DUALPI2_ALPHA, + dualpi2_unscale_alpha_beta(READ_ONCE(q->pi2_alpha))) || + nla_put_u32(skb, TCA_DUALPI2_BETA, + dualpi2_unscale_alpha_beta(READ_ONCE(q->pi2_beta))) || + nla_put_u32(skb, TCA_DUALPI2_STEP_THRESH_US, + convert_ns_to_usec(step_th)) || + nla_put_u32(skb, TCA_DUALPI2_MIN_QLEN_STEP, + READ_ONCE(q->min_qlen_step)) || + nla_put_u8(skb, TCA_DUALPI2_COUPLING, + READ_ONCE(q->coupling_factor)) || + nla_put_u8(skb, TCA_DUALPI2_DROP_OVERLOAD, + READ_ONCE(q->drop_overload)) || + nla_put_u8(skb, TCA_DUALPI2_DROP_EARLY, + READ_ONCE(q->drop_early)) || + nla_put_u8(skb, TCA_DUALPI2_C_PROTECTION, + READ_ONCE(q->c_protection_wc)) || + nla_put_u8(skb, TCA_DUALPI2_ECN_MASK, READ_ONCE(q->ecn_mask)) || + nla_put_u8(skb, TCA_DUALPI2_SPLIT_GSO, READ_ONCE(q->split_gso)))) + goto nla_put_failure; + + return nla_nest_end(skb, opts); + +nla_put_failure: + nla_nest_cancel(skb, opts); + return -1; +} + +static int dualpi2_dump_stats(struct Qdisc *sch, struct gnet_dump *d) +{ + struct dualpi2_sched_data *q = qdisc_priv(sch); + struct tc_dualpi2_xstats st = { + .prob = q->pi2_prob, + .packets_in_c = q->packets_in_c, + .packets_in_l = q->packets_in_l, + .maxq = q->maxq, + .ecn_mark = q->ecn_mark, + .credit = q->c_protection_credit, + .step_marks = q->step_marks, + .memory_used = q->memory_used, + .max_memory_used = q->max_memory_used, + .memory_limit = q->memory_limit, + }; + u64 qc, ql; + + get_queue_delays(q, &qc, &ql); + st.delay_l = convert_ns_to_usec(ql); + st.delay_c = convert_ns_to_usec(qc); + return gnet_stats_copy_app(d, &st, sizeof(st)); +} + /* Reset both L-queue and C-queue, internal packet counters, PI probability, * C-queue protection credit, and timestamps, while preserving current * configuration of DUALPI2. @@ -564,6 +682,8 @@ static struct Qdisc_ops dualpi2_qdisc_ops __read_mostly = { .destroy = dualpi2_destroy, .reset = dualpi2_reset, .change = dualpi2_change, + .dump = dualpi2_dump, + .dump_stats = dualpi2_dump_stats, .owner = THIS_MODULE, }; -- cgit v1.2.3 From 8e7583a4f65f3dbf3e8deb4e60f3679c276bef62 Mon Sep 17 00:00:00 2001 From: Samiullah Khawaja Date: Wed, 23 Jul 2025 01:30:31 +0000 Subject: net: define an enum for the napi threaded state Instead of using '0' and '1' for napi threaded state use an enum with 'disabled' and 'enabled' states. Tested: ./tools/testing/selftests/net/nl_netdev.py TAP version 13 1..7 ok 1 nl_netdev.empty_check ok 2 nl_netdev.lo_check ok 3 nl_netdev.page_pool_check ok 4 nl_netdev.napi_list_check ok 5 nl_netdev.dev_set_threaded ok 6 nl_netdev.napi_set_threaded ok 7 nl_netdev.nsim_rxq_reset_down # Totals: pass:7 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Samiullah Khawaja Link: https://patch.msgid.link/20250723013031.2911384-4-skhawaja@google.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/netdev.yaml | 13 +++++--- .../networking/net_cachelines/net_device.rst | 2 +- include/linux/netdevice.h | 10 +++--- include/uapi/linux/netdev.h | 5 +++ net/core/dev.c | 12 +++++--- net/core/dev.h | 13 +++++--- net/core/dev_api.c | 3 +- net/core/netdev-genl-gen.c | 2 +- net/core/netdev-genl.c | 2 +- tools/include/uapi/linux/netdev.h | 5 +++ tools/testing/selftests/net/nl_netdev.py | 36 +++++++++++----------- 11 files changed, 62 insertions(+), 41 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index 85d0ea6ac426..c035dc0f64fd 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -85,6 +85,10 @@ definitions: name: qstats-scope type: flags entries: [queue] + - + name: napi-threaded + type: enum + entries: [disabled, enabled] attribute-sets: - @@ -286,11 +290,10 @@ attribute-sets: - name: threaded doc: Whether the NAPI is configured to operate in threaded polling - mode. If this is set to 1 then the NAPI context operates in - threaded polling mode. - type: uint - checks: - max: 1 + mode. If this is set to enabled then the NAPI context operates + in threaded polling mode. + type: u32 + enum: napi-threaded - name: xsk-info attributes: [] diff --git a/Documentation/networking/net_cachelines/net_device.rst b/Documentation/networking/net_cachelines/net_device.rst index 2d3dc4692d20..1c19bb7705df 100644 --- a/Documentation/networking/net_cachelines/net_device.rst +++ b/Documentation/networking/net_cachelines/net_device.rst @@ -68,6 +68,7 @@ unsigned_char addr_assign_type unsigned_char addr_len unsigned_char upper_level unsigned_char lower_level +u8 threaded napi_poll(napi_enable,netif_set_threaded) unsigned_short neigh_priv_len unsigned_short padded unsigned_short dev_id @@ -165,7 +166,6 @@ struct sfp_bus* sfp_bus struct lock_class_key* qdisc_tx_busylock bool proto_down unsigned:1 wol_enabled -unsigned:1 threaded napi_poll(napi_enable,netif_set_threaded) unsigned_long:1 see_all_hwtstamp_requests unsigned_long:1 change_proto_down unsigned_long:1 netns_immutable diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index a97c9a337d6b..5e5de4b0a433 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -369,7 +369,7 @@ struct napi_config { u64 irq_suspend_timeout; u32 defer_hard_irqs; cpumask_t affinity_mask; - bool threaded; + u8 threaded; unsigned int napi_id; }; @@ -590,7 +590,8 @@ static inline bool napi_complete(struct napi_struct *n) } void netif_threaded_enable(struct net_device *dev); -int dev_set_threaded(struct net_device *dev, bool threaded); +int dev_set_threaded(struct net_device *dev, + enum netdev_napi_threaded threaded); void napi_disable(struct napi_struct *n); void napi_disable_locked(struct napi_struct *n); @@ -1872,6 +1873,7 @@ enum netdev_reg_state { * @addr_len: Hardware address length * @upper_level: Maximum depth level of upper devices. * @lower_level: Maximum depth level of lower devices. + * @threaded: napi threaded state. * @neigh_priv_len: Used in neigh_alloc() * @dev_id: Used to differentiate devices that share * the same link layer address @@ -2011,8 +2013,6 @@ enum netdev_reg_state { * switch driver and used to set the phys state of the * switch port. * - * @threaded: napi threaded mode is enabled - * * @irq_affinity_auto: driver wants the core to store and re-assign the IRQ * affinity. Set by netif_enable_irq_affinity(), then * the driver must create a persistent napi by @@ -2248,6 +2248,7 @@ struct net_device { unsigned char addr_len; unsigned char upper_level; unsigned char lower_level; + u8 threaded; unsigned short neigh_priv_len; unsigned short dev_id; @@ -2429,7 +2430,6 @@ struct net_device { struct sfp_bus *sfp_bus; struct lock_class_key *qdisc_tx_busylock; bool proto_down; - bool threaded; bool irq_affinity_auto; bool rx_cpu_rmap_auto; diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h index 1f3719a9a0eb..48eb49aa03d4 100644 --- a/include/uapi/linux/netdev.h +++ b/include/uapi/linux/netdev.h @@ -77,6 +77,11 @@ enum netdev_qstats_scope { NETDEV_QSTATS_SCOPE_QUEUE = 1, }; +enum netdev_napi_threaded { + NETDEV_NAPI_THREADED_DISABLED, + NETDEV_NAPI_THREADED_ENABLED, +}; + enum { NETDEV_A_DEV_IFINDEX = 1, NETDEV_A_DEV_PAD, diff --git a/net/core/dev.c b/net/core/dev.c index f28661d6f5ea..1c6e755841ce 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -6963,7 +6963,8 @@ static void napi_stop_kthread(struct napi_struct *napi) napi->thread = NULL; } -int napi_set_threaded(struct napi_struct *napi, bool threaded) +int napi_set_threaded(struct napi_struct *napi, + enum netdev_napi_threaded threaded) { if (threaded) { if (!napi->thread) { @@ -6988,7 +6989,8 @@ int napi_set_threaded(struct napi_struct *napi, bool threaded) return 0; } -int netif_set_threaded(struct net_device *dev, bool threaded) +int netif_set_threaded(struct net_device *dev, + enum netdev_napi_threaded threaded) { struct napi_struct *napi; int err = 0; @@ -7000,7 +7002,7 @@ int netif_set_threaded(struct net_device *dev, bool threaded) if (!napi->thread) { err = napi_kthread_create(napi); if (err) { - threaded = false; + threaded = NETDEV_NAPI_THREADED_DISABLED; break; } } @@ -7043,7 +7045,7 @@ int netif_set_threaded(struct net_device *dev, bool threaded) */ void netif_threaded_enable(struct net_device *dev) { - WARN_ON_ONCE(netif_set_threaded(dev, true)); + WARN_ON_ONCE(netif_set_threaded(dev, NETDEV_NAPI_THREADED_ENABLED)); } EXPORT_SYMBOL(netif_threaded_enable); @@ -7360,7 +7362,7 @@ void netif_napi_add_weight_locked(struct net_device *dev, * threaded mode will not be enabled in napi_enable(). */ if (dev->threaded && napi_kthread_create(napi)) - dev->threaded = false; + dev->threaded = NETDEV_NAPI_THREADED_DISABLED; netif_napi_set_irq_locked(napi, -1); } EXPORT_SYMBOL(netif_napi_add_weight_locked); diff --git a/net/core/dev.h b/net/core/dev.h index f5b567310908..ab69edc0c3e3 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -315,14 +315,19 @@ static inline void napi_set_irq_suspend_timeout(struct napi_struct *n, WRITE_ONCE(n->irq_suspend_timeout, timeout); } -static inline bool napi_get_threaded(struct napi_struct *n) +static inline enum netdev_napi_threaded napi_get_threaded(struct napi_struct *n) { - return test_bit(NAPI_STATE_THREADED, &n->state); + if (test_bit(NAPI_STATE_THREADED, &n->state)) + return NETDEV_NAPI_THREADED_ENABLED; + + return NETDEV_NAPI_THREADED_DISABLED; } -int napi_set_threaded(struct napi_struct *n, bool threaded); +int napi_set_threaded(struct napi_struct *n, + enum netdev_napi_threaded threaded); -int netif_set_threaded(struct net_device *dev, bool threaded); +int netif_set_threaded(struct net_device *dev, + enum netdev_napi_threaded threaded); int rps_cpumask_housekeeping(struct cpumask *mask); diff --git a/net/core/dev_api.c b/net/core/dev_api.c index dd7f57013ce5..f28852078aa6 100644 --- a/net/core/dev_api.c +++ b/net/core/dev_api.c @@ -368,7 +368,8 @@ void netdev_state_change(struct net_device *dev) } EXPORT_SYMBOL(netdev_state_change); -int dev_set_threaded(struct net_device *dev, bool threaded) +int dev_set_threaded(struct net_device *dev, + enum netdev_napi_threaded threaded) { int ret; diff --git a/net/core/netdev-genl-gen.c b/net/core/netdev-genl-gen.c index 0994bd68a7e6..e9a2a6f26cb7 100644 --- a/net/core/netdev-genl-gen.c +++ b/net/core/netdev-genl-gen.c @@ -97,7 +97,7 @@ static const struct nla_policy netdev_napi_set_nl_policy[NETDEV_A_NAPI_THREADED [NETDEV_A_NAPI_DEFER_HARD_IRQS] = NLA_POLICY_FULL_RANGE(NLA_U32, &netdev_a_napi_defer_hard_irqs_range), [NETDEV_A_NAPI_GRO_FLUSH_TIMEOUT] = { .type = NLA_UINT, }, [NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT] = { .type = NLA_UINT, }, - [NETDEV_A_NAPI_THREADED] = NLA_POLICY_MAX(NLA_UINT, 1), + [NETDEV_A_NAPI_THREADED] = NLA_POLICY_MAX(NLA_U32, 1), }; /* NETDEV_CMD_BIND_TX - do */ diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index 5875df372415..6314eb7bdf69 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -333,7 +333,7 @@ netdev_nl_napi_set_config(struct napi_struct *napi, struct genl_info *info) int ret; threaded = nla_get_uint(info->attrs[NETDEV_A_NAPI_THREADED]); - ret = napi_set_threaded(napi, !!threaded); + ret = napi_set_threaded(napi, threaded); if (ret) return ret; } diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h index 1f3719a9a0eb..48eb49aa03d4 100644 --- a/tools/include/uapi/linux/netdev.h +++ b/tools/include/uapi/linux/netdev.h @@ -77,6 +77,11 @@ enum netdev_qstats_scope { NETDEV_QSTATS_SCOPE_QUEUE = 1, }; +enum netdev_napi_threaded { + NETDEV_NAPI_THREADED_DISABLED, + NETDEV_NAPI_THREADED_ENABLED, +}; + enum { NETDEV_A_DEV_IFINDEX = 1, NETDEV_A_DEV_PAD, diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py index c8ffade79a52..5c66421ab8aa 100755 --- a/tools/testing/selftests/net/nl_netdev.py +++ b/tools/testing/selftests/net/nl_netdev.py @@ -52,14 +52,14 @@ def napi_set_threaded(nf) -> None: napi1_id = napis[1]['id'] # set napi threaded and verify - nf.napi_set({'id': napi0_id, 'threaded': 1}) + nf.napi_set({'id': napi0_id, 'threaded': "enabled"}) napi0 = nf.napi_get({'id': napi0_id}) - ksft_eq(napi0['threaded'], 1) + ksft_eq(napi0['threaded'], "enabled") ksft_ne(napi0.get('pid'), None) # check it is not set for napi1 napi1 = nf.napi_get({'id': napi1_id}) - ksft_eq(napi1['threaded'], 0) + ksft_eq(napi1['threaded'], "disabled") ksft_eq(napi1.get('pid'), None) ip(f"link set dev {nsim.ifname} down") @@ -67,18 +67,18 @@ def napi_set_threaded(nf) -> None: # verify if napi threaded is still set napi0 = nf.napi_get({'id': napi0_id}) - ksft_eq(napi0['threaded'], 1) + ksft_eq(napi0['threaded'], "enabled") ksft_ne(napi0.get('pid'), None) # check it is still not set for napi1 napi1 = nf.napi_get({'id': napi1_id}) - ksft_eq(napi1['threaded'], 0) + ksft_eq(napi1['threaded'], "disabled") ksft_eq(napi1.get('pid'), None) # unset napi threaded and verify - nf.napi_set({'id': napi0_id, 'threaded': 0}) + nf.napi_set({'id': napi0_id, 'threaded': "disabled"}) napi0 = nf.napi_get({'id': napi0_id}) - ksft_eq(napi0['threaded'], 0) + ksft_eq(napi0['threaded'], "disabled") ksft_eq(napi0.get('pid'), None) # set threaded at device level @@ -86,10 +86,10 @@ def napi_set_threaded(nf) -> None: # check napi threaded is set for both napis napi0 = nf.napi_get({'id': napi0_id}) - ksft_eq(napi0['threaded'], 1) + ksft_eq(napi0['threaded'], "enabled") ksft_ne(napi0.get('pid'), None) napi1 = nf.napi_get({'id': napi1_id}) - ksft_eq(napi1['threaded'], 1) + ksft_eq(napi1['threaded'], "enabled") ksft_ne(napi1.get('pid'), None) # unset threaded at device level @@ -97,16 +97,16 @@ def napi_set_threaded(nf) -> None: # check napi threaded is unset for both napis napi0 = nf.napi_get({'id': napi0_id}) - ksft_eq(napi0['threaded'], 0) + ksft_eq(napi0['threaded'], "disabled") ksft_eq(napi0.get('pid'), None) napi1 = nf.napi_get({'id': napi1_id}) - ksft_eq(napi1['threaded'], 0) + ksft_eq(napi1['threaded'], "disabled") ksft_eq(napi1.get('pid'), None) # set napi threaded for napi0 nf.napi_set({'id': napi0_id, 'threaded': 1}) napi0 = nf.napi_get({'id': napi0_id}) - ksft_eq(napi0['threaded'], 1) + ksft_eq(napi0['threaded'], "enabled") ksft_ne(napi0.get('pid'), None) # unset threaded at device level @@ -114,10 +114,10 @@ def napi_set_threaded(nf) -> None: # check napi threaded is unset for both napis napi0 = nf.napi_get({'id': napi0_id}) - ksft_eq(napi0['threaded'], 0) + ksft_eq(napi0['threaded'], "disabled") ksft_eq(napi0.get('pid'), None) napi1 = nf.napi_get({'id': napi1_id}) - ksft_eq(napi1['threaded'], 0) + ksft_eq(napi1['threaded'], "disabled") ksft_eq(napi1.get('pid'), None) def dev_set_threaded(nf) -> None: @@ -141,10 +141,10 @@ def dev_set_threaded(nf) -> None: # check napi threaded is set for both napis napi0 = nf.napi_get({'id': napi0_id}) - ksft_eq(napi0['threaded'], 1) + ksft_eq(napi0['threaded'], "enabled") ksft_ne(napi0.get('pid'), None) napi1 = nf.napi_get({'id': napi1_id}) - ksft_eq(napi1['threaded'], 1) + ksft_eq(napi1['threaded'], "enabled") ksft_ne(napi1.get('pid'), None) # unset threaded @@ -152,10 +152,10 @@ def dev_set_threaded(nf) -> None: # check napi threaded is unset for both napis napi0 = nf.napi_get({'id': napi0_id}) - ksft_eq(napi0['threaded'], 0) + ksft_eq(napi0['threaded'], "disabled") ksft_eq(napi0.get('pid'), None) napi1 = nf.napi_get({'id': napi1_id}) - ksft_eq(napi1['threaded'], 0) + ksft_eq(napi1['threaded'], "disabled") ksft_eq(napi1.get('pid'), None) def nsim_rxq_reset_down(nf) -> None: -- cgit v1.2.3 From bc8c43adfdc57c8253884fc1853cb6679cd5953d Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Tue, 8 Jul 2025 15:04:02 +0200 Subject: netfilter: nfnetlink_hook: Dump flowtable info Introduce NFNL_HOOK_TYPE_NFT_FLOWTABLE to distinguish flowtable hooks from base chain ones. Nested attributes are shared with the old NFTABLES hook info type since they fit apart from their misleading name. Old nftables in user space will ignore this new hook type and thus continue to print flowtable hooks just like before, e.g.: | family netdev { | hook ingress device test0 { | 0000000000 nf_flow_offload_ip_hook [nf_flow_table] | } | } With this patch in place and support for the new hook info type, output becomes more useful: | family netdev { | hook ingress device test0 { | 0000000000 flowtable ip mytable myft [nf_flow_table] | } | } Suggested-by: Florian Westphal Signed-off-by: Phil Sutter Reviewed-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- include/linux/netfilter.h | 1 + include/uapi/linux/netfilter/nfnetlink_hook.h | 2 ++ net/netfilter/nf_tables_api.c | 24 +++++++++--------- net/netfilter/nfnetlink_hook.c | 35 +++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 11 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h index 5f896fcc074d..efbbfa770d66 100644 --- a/include/linux/netfilter.h +++ b/include/linux/netfilter.h @@ -92,6 +92,7 @@ enum nf_hook_ops_type { NF_HOOK_OP_UNDEFINED, NF_HOOK_OP_NF_TABLES, NF_HOOK_OP_BPF, + NF_HOOK_OP_NFT_FT, }; struct nf_hook_ops { diff --git a/include/uapi/linux/netfilter/nfnetlink_hook.h b/include/uapi/linux/netfilter/nfnetlink_hook.h index 84a561a74b98..1a2c4d6424b5 100644 --- a/include/uapi/linux/netfilter/nfnetlink_hook.h +++ b/include/uapi/linux/netfilter/nfnetlink_hook.h @@ -61,10 +61,12 @@ enum nfnl_hook_chain_desc_attributes { * * @NFNL_HOOK_TYPE_NFTABLES: nf_tables base chain * @NFNL_HOOK_TYPE_BPF: bpf program + * @NFNL_HOOK_TYPE_NFT_FLOWTABLE: nf_tables flowtable */ enum nfnl_hook_chaintype { NFNL_HOOK_TYPE_NFTABLES = 0x1, NFNL_HOOK_TYPE_BPF, + NFNL_HOOK_TYPE_NFT_FLOWTABLE, }; /** diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 04795af6e586..13d0ed9d1895 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -8895,11 +8895,12 @@ static int nft_flowtable_parse_hook(const struct nft_ctx *ctx, list_for_each_entry(hook, &flowtable_hook->list, list) { list_for_each_entry(ops, &hook->ops_list, list) { - ops->pf = NFPROTO_NETDEV; - ops->hooknum = flowtable_hook->num; - ops->priority = flowtable_hook->priority; - ops->priv = &flowtable->data; - ops->hook = flowtable->data.type->hook; + ops->pf = NFPROTO_NETDEV; + ops->hooknum = flowtable_hook->num; + ops->priority = flowtable_hook->priority; + ops->priv = &flowtable->data; + ops->hook = flowtable->data.type->hook; + ops->hook_ops_type = NF_HOOK_OP_NFT_FT; } } @@ -9727,12 +9728,13 @@ static int nft_flowtable_event(unsigned long event, struct net_device *dev, if (!ops) return 1; - ops->pf = NFPROTO_NETDEV; - ops->hooknum = flowtable->hooknum; - ops->priority = flowtable->data.priority; - ops->priv = &flowtable->data; - ops->hook = flowtable->data.type->hook; - ops->dev = dev; + ops->pf = NFPROTO_NETDEV; + ops->hooknum = flowtable->hooknum; + ops->priority = flowtable->data.priority; + ops->priv = &flowtable->data; + ops->hook = flowtable->data.type->hook; + ops->hook_ops_type = NF_HOOK_OP_NFT_FT; + ops->dev = dev; if (nft_register_flowtable_ops(dev_net(dev), flowtable, ops)) { kfree(ops); diff --git a/net/netfilter/nfnetlink_hook.c b/net/netfilter/nfnetlink_hook.c index cd4056527ede..92d869317cba 100644 --- a/net/netfilter/nfnetlink_hook.c +++ b/net/netfilter/nfnetlink_hook.c @@ -156,6 +156,38 @@ static int nfnl_hook_put_nft_chain_info(struct sk_buff *nlskb, return 0; } +static int nfnl_hook_put_nft_ft_info(struct sk_buff *nlskb, + const struct nfnl_dump_hook_data *ctx, + unsigned int seq, + struct nf_flowtable *nf_ft) +{ + struct nft_flowtable *ft = + container_of(nf_ft, struct nft_flowtable, data); + struct net *net = sock_net(nlskb->sk); + struct nlattr *nest; + int ret = 0; + + if (WARN_ON_ONCE(!nf_ft)) + return 0; + + if (!nft_is_active(net, ft)) + return 0; + + nest = nfnl_start_info_type(nlskb, NFNL_HOOK_TYPE_NFT_FLOWTABLE); + if (!nest) + return -EMSGSIZE; + + ret = nfnl_hook_put_nft_info_desc(nlskb, ft->table->name, + ft->name, ft->table->family); + if (ret) { + nla_nest_cancel(nlskb, nest); + return ret; + } + + nla_nest_end(nlskb, nest); + return 0; +} + static int nfnl_hook_dump_one(struct sk_buff *nlskb, const struct nfnl_dump_hook_data *ctx, const struct nf_hook_ops *ops, @@ -223,6 +255,9 @@ static int nfnl_hook_dump_one(struct sk_buff *nlskb, case NF_HOOK_OP_BPF: ret = nfnl_hook_put_bpf_prog_info(nlskb, ctx, seq, ops->priv); break; + case NF_HOOK_OP_NFT_FT: + ret = nfnl_hook_put_nft_ft_info(nlskb, ctx, seq, ops->priv); + break; case NF_HOOK_OP_UNDEFINED: break; default: -- cgit v1.2.3 From f24987ef6959a7efaf79bffd265522c3df18d431 Mon Sep 17 00:00:00 2001 From: Gabriel Goller Date: Tue, 22 Jul 2025 10:18:45 +0200 Subject: ipv6: add `force_forwarding` sysctl to enable per-interface forwarding It is currently impossible to enable ipv6 forwarding on a per-interface basis like in ipv4. To enable forwarding on an ipv6 interface we need to enable it on all interfaces and disable it on the other interfaces using a netfilter rule. This is especially cumbersome if you have lots of interfaces and only want to enable forwarding on a few. According to the sysctl docs [0] the `net.ipv6.conf.all.forwarding` enables forwarding for all interfaces, while the interface-specific `net.ipv6.conf..forwarding` configures the interface Host/Router configuration. Introduce a new sysctl flag `force_forwarding`, which can be set on every interface. The ip6_forwarding function will then check if the global forwarding flag OR the force_forwarding flag is active and forward the packet. To preserve backwards-compatibility reset the flag (on all interfaces) to 0 if the net.ipv6.conf.all.forwarding flag is set to 0. Add a short selftest that checks if a packet gets forwarded with and without `force_forwarding`. [0]: https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt Acked-by: Nicolas Dichtel Signed-off-by: Gabriel Goller Link: https://patch.msgid.link/20250722081847.132632-1-g.goller@proxmox.com Signed-off-by: Jakub Kicinski --- Documentation/networking/ip-sysctl.rst | 8 +- include/linux/ipv6.h | 1 + include/uapi/linux/ipv6.h | 1 + include/uapi/linux/netconf.h | 1 + include/uapi/linux/sysctl.h | 1 + net/ipv6/addrconf.c | 82 ++++++++++++++++ net/ipv6/ip6_output.c | 3 +- tools/testing/selftests/net/Makefile | 1 + .../testing/selftests/net/ipv6_force_forwarding.sh | 105 +++++++++++++++++++++ 9 files changed, 200 insertions(+), 3 deletions(-) create mode 100755 tools/testing/selftests/net/ipv6_force_forwarding.sh (limited to 'include/uapi/linux') diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index 14700ea77e75..bb620f554598 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -2543,8 +2543,8 @@ conf/all/disable_ipv6 - BOOLEAN conf/all/forwarding - BOOLEAN Enable global IPv6 forwarding between all interfaces. - IPv4 and IPv6 work differently here; e.g. netfilter must be used - to control which interfaces may forward packets and which not. + IPv4 and IPv6 work differently here; the ``force_forwarding`` flag must + be used to control which interfaces may forward packets. This also sets all interfaces' Host/Router setting 'forwarding' to the specified value. See below for details. @@ -2561,6 +2561,10 @@ proxy_ndp - BOOLEAN Default: 0 (disabled) +force_forwarding - BOOLEAN + Enable forwarding on this interface only -- regardless of the setting on + ``conf/all/forwarding``. When setting ``conf.all.forwarding`` to 0, + the ``force_forwarding`` flag will be reset on all interfaces. fwmark_reflect - BOOLEAN Controls the fwmark of kernel-generated IPv6 reply packets that are not diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h index db0eb0d86b64..bc6ec2959173 100644 --- a/include/linux/ipv6.h +++ b/include/linux/ipv6.h @@ -17,6 +17,7 @@ struct ipv6_devconf { __s32 hop_limit; __s32 mtu6; __s32 forwarding; + __s32 force_forwarding; __s32 disable_policy; __s32 proxy_ndp; __cacheline_group_end(ipv6_devconf_read_txrx); diff --git a/include/uapi/linux/ipv6.h b/include/uapi/linux/ipv6.h index cf592d7b630f..d4d3ae774b26 100644 --- a/include/uapi/linux/ipv6.h +++ b/include/uapi/linux/ipv6.h @@ -199,6 +199,7 @@ enum { DEVCONF_NDISC_EVICT_NOCARRIER, DEVCONF_ACCEPT_UNTRACKED_NA, DEVCONF_ACCEPT_RA_MIN_LFT, + DEVCONF_FORCE_FORWARDING, DEVCONF_MAX }; diff --git a/include/uapi/linux/netconf.h b/include/uapi/linux/netconf.h index fac4edd55379..1c8c84d65ae3 100644 --- a/include/uapi/linux/netconf.h +++ b/include/uapi/linux/netconf.h @@ -19,6 +19,7 @@ enum { NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN, NETCONFA_INPUT, NETCONFA_BC_FORWARDING, + NETCONFA_FORCE_FORWARDING, __NETCONFA_MAX }; #define NETCONFA_MAX (__NETCONFA_MAX - 1) diff --git a/include/uapi/linux/sysctl.h b/include/uapi/linux/sysctl.h index 8981f00204db..63d1464cb71c 100644 --- a/include/uapi/linux/sysctl.h +++ b/include/uapi/linux/sysctl.h @@ -573,6 +573,7 @@ enum { NET_IPV6_ACCEPT_RA_FROM_LOCAL=26, NET_IPV6_ACCEPT_RA_RT_INFO_MIN_PLEN=27, NET_IPV6_RA_DEFRTR_METRIC=28, + NET_IPV6_FORCE_FORWARDING=29, __NET_IPV6_MAX }; diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 4f1d7d110302..81a067a2e526 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -239,6 +239,7 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = { .ndisc_evict_nocarrier = 1, .ra_honor_pio_life = 0, .ra_honor_pio_pflag = 0, + .force_forwarding = 0, }; static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = { @@ -303,6 +304,7 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = { .ndisc_evict_nocarrier = 1, .ra_honor_pio_life = 0, .ra_honor_pio_pflag = 0, + .force_forwarding = 0, }; /* Check if link is ready: is it up and is a valid qdisc available */ @@ -857,6 +859,9 @@ static void addrconf_forward_change(struct net *net, __s32 newf) idev = __in6_dev_get_rtnl_net(dev); if (idev) { int changed = (!idev->cnf.forwarding) ^ (!newf); + /* Disabling all.forwarding sets 0 to force_forwarding for all interfaces */ + if (newf == 0) + WRITE_ONCE(idev->cnf.force_forwarding, 0); WRITE_ONCE(idev->cnf.forwarding, newf); if (changed) @@ -5710,6 +5715,7 @@ static void ipv6_store_devconf(const struct ipv6_devconf *cnf, array[DEVCONF_ACCEPT_UNTRACKED_NA] = READ_ONCE(cnf->accept_untracked_na); array[DEVCONF_ACCEPT_RA_MIN_LFT] = READ_ONCE(cnf->accept_ra_min_lft); + array[DEVCONF_FORCE_FORWARDING] = READ_ONCE(cnf->force_forwarding); } static inline size_t inet6_ifla6_size(void) @@ -6738,6 +6744,75 @@ static int addrconf_sysctl_disable_policy(const struct ctl_table *ctl, int write return ret; } +static void addrconf_force_forward_change(struct net *net, __s32 newf) +{ + struct net_device *dev; + struct inet6_dev *idev; + + for_each_netdev(net, dev) { + idev = __in6_dev_get_rtnl_net(dev); + if (idev) { + int changed = (!idev->cnf.force_forwarding) ^ (!newf); + + WRITE_ONCE(idev->cnf.force_forwarding, newf); + if (changed) + inet6_netconf_notify_devconf(dev_net(dev), RTM_NEWNETCONF, + NETCONFA_FORCE_FORWARDING, + dev->ifindex, &idev->cnf); + } + } +} + +static int addrconf_sysctl_force_forwarding(const struct ctl_table *ctl, int write, + void *buffer, size_t *lenp, loff_t *ppos) +{ + struct inet6_dev *idev = ctl->extra1; + struct ctl_table tmp_ctl = *ctl; + struct net *net = ctl->extra2; + int *valp = ctl->data; + int new_val = *valp; + int old_val = *valp; + loff_t pos = *ppos; + int ret; + + tmp_ctl.extra1 = SYSCTL_ZERO; + tmp_ctl.extra2 = SYSCTL_ONE; + tmp_ctl.data = &new_val; + + ret = proc_douintvec_minmax(&tmp_ctl, write, buffer, lenp, ppos); + + if (write && old_val != new_val) { + if (!rtnl_net_trylock(net)) + return restart_syscall(); + + WRITE_ONCE(*valp, new_val); + + if (valp == &net->ipv6.devconf_dflt->force_forwarding) { + inet6_netconf_notify_devconf(net, RTM_NEWNETCONF, + NETCONFA_FORCE_FORWARDING, + NETCONFA_IFINDEX_DEFAULT, + net->ipv6.devconf_dflt); + } else if (valp == &net->ipv6.devconf_all->force_forwarding) { + inet6_netconf_notify_devconf(net, RTM_NEWNETCONF, + NETCONFA_FORCE_FORWARDING, + NETCONFA_IFINDEX_ALL, + net->ipv6.devconf_all); + + addrconf_force_forward_change(net, new_val); + } else { + inet6_netconf_notify_devconf(net, RTM_NEWNETCONF, + NETCONFA_FORCE_FORWARDING, + idev->dev->ifindex, + &idev->cnf); + } + rtnl_net_unlock(net); + } + + if (ret) + *ppos = pos; + return ret; +} + static int minus_one = -1; static const int two_five_five = 255; static u32 ioam6_if_id_max = U16_MAX; @@ -7208,6 +7283,13 @@ static const struct ctl_table addrconf_sysctl[] = { .extra1 = SYSCTL_ZERO, .extra2 = SYSCTL_TWO, }, + { + .procname = "force_forwarding", + .data = &ipv6_devconf.force_forwarding, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = addrconf_sysctl_force_forwarding, + }, }; static int __addrconf_sysctl_register(struct net *net, char *dev_name, diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c index 0412f8544695..1e1410237b6e 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -511,7 +511,8 @@ int ip6_forward(struct sk_buff *skb) u32 mtu; idev = __in6_dev_get_safely(dev_get_by_index_rcu(net, IP6CB(skb)->iif)); - if (READ_ONCE(net->ipv6.devconf_all->forwarding) == 0) + if (!READ_ONCE(net->ipv6.devconf_all->forwarding) && + (!idev || !READ_ONCE(idev->cnf.force_forwarding))) goto error; if (skb->pkt_type != PACKET_HOST) diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 13e2678d418b..b31a71f2b372 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -116,6 +116,7 @@ TEST_GEN_FILES += skf_net_off TEST_GEN_FILES += tfo TEST_PROGS += tfo_passive.sh TEST_PROGS += broadcast_pmtu.sh +TEST_PROGS += ipv6_force_forwarding.sh # YNL files, must be before "include ..lib.mk" YNL_GEN_FILES := busy_poller netlink-dumps diff --git a/tools/testing/selftests/net/ipv6_force_forwarding.sh b/tools/testing/selftests/net/ipv6_force_forwarding.sh new file mode 100755 index 000000000000..bf0243366caa --- /dev/null +++ b/tools/testing/selftests/net/ipv6_force_forwarding.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Test IPv6 force_forwarding interface property +# +# This test verifies that the force_forwarding property works correctly: +# - When global forwarding is disabled, packets are not forwarded normally +# - When force_forwarding is enabled on an interface, packets are forwarded +# regardless of the global forwarding setting + +source lib.sh + +cleanup() { + cleanup_ns $ns1 $ns2 $ns3 +} + +trap cleanup EXIT + +setup_test() { + # Create three namespaces: sender, router, receiver + setup_ns ns1 ns2 ns3 + + # Create veth pairs: ns1 <-> ns2 <-> ns3 + ip link add name veth12 type veth peer name veth21 + ip link add name veth23 type veth peer name veth32 + + # Move interfaces to namespaces + ip link set veth12 netns $ns1 + ip link set veth21 netns $ns2 + ip link set veth23 netns $ns2 + ip link set veth32 netns $ns3 + + # Configure interfaces + ip -n $ns1 addr add 2001:db8:1::1/64 dev veth12 nodad + ip -n $ns2 addr add 2001:db8:1::2/64 dev veth21 nodad + ip -n $ns2 addr add 2001:db8:2::1/64 dev veth23 nodad + ip -n $ns3 addr add 2001:db8:2::2/64 dev veth32 nodad + + # Bring up interfaces + ip -n $ns1 link set veth12 up + ip -n $ns2 link set veth21 up + ip -n $ns2 link set veth23 up + ip -n $ns3 link set veth32 up + + # Add routes + ip -n $ns1 route add 2001:db8:2::/64 via 2001:db8:1::2 + ip -n $ns3 route add 2001:db8:1::/64 via 2001:db8:2::1 + + # Disable global forwarding + ip netns exec $ns2 sysctl -qw net.ipv6.conf.all.forwarding=0 +} + +test_force_forwarding() { + local ret=0 + + echo "TEST: force_forwarding functionality" + + # Check if force_forwarding sysctl exists + if ! ip netns exec $ns2 test -f /proc/sys/net/ipv6/conf/veth21/force_forwarding; then + echo "SKIP: force_forwarding not available" + return $ksft_skip + fi + + # Test 1: Without force_forwarding, ping should fail + ip netns exec $ns2 sysctl -qw net.ipv6.conf.veth21.force_forwarding=0 + ip netns exec $ns2 sysctl -qw net.ipv6.conf.veth23.force_forwarding=0 + + if ip netns exec $ns1 ping -6 -c 1 -W 2 2001:db8:2::2 &>/dev/null; then + echo "FAIL: ping succeeded when forwarding disabled" + ret=1 + else + echo "PASS: forwarding disabled correctly" + fi + + # Test 2: With force_forwarding enabled, ping should succeed + ip netns exec $ns2 sysctl -qw net.ipv6.conf.veth21.force_forwarding=1 + ip netns exec $ns2 sysctl -qw net.ipv6.conf.veth23.force_forwarding=1 + + if ip netns exec $ns1 ping -6 -c 1 -W 2 2001:db8:2::2 &>/dev/null; then + echo "PASS: force_forwarding enabled forwarding" + else + echo "FAIL: ping failed with force_forwarding enabled" + ret=1 + fi + + return $ret +} + +echo "IPv6 force_forwarding test" +echo "==========================" + +setup_test +test_force_forwarding +ret=$? + +if [ $ret -eq 0 ]; then + echo "OK" + exit 0 +elif [ $ret -eq $ksft_skip ]; then + echo "SKIP" + exit $ksft_skip +else + echo "FAIL" + exit 1 +fi -- cgit v1.2.3