Linux wireless drivers development
 help / color / mirror / Atom feed
From: jeff.chen_1@oss.nxp.com
To: linux-wireless@vger.kernel.org
Cc: johannes@sipsolutions.net, francesco@dolcini.it,
	Jeff Chen <jeff.chen_1@nxp.com>
Subject: [PATCH 2/2] wifi: nxpwifi: add netlink vendor command for TX power limits
Date: Thu,  1 Oct 2026 18:06:40 +0800	[thread overview]
Message-ID: <20261001100640.2829946-3-jeff.chen_1@oss.nxp.com> (raw)
In-Reply-To: <20261001100640.2829946-1-jeff.chen_1@oss.nxp.com>

From: Jeff Chen <jeff.chen_1@nxp.com>

Add a cfg80211 vendor command that allows user space to retrieve and
update firmware TX power limit data through a stable netlink interface.

The firmware exposes TX power limits through the channel TRPC command.
The associated payload is a firmware-defined, chipset-specific table
containing TX power limit information.

The table format is part of the firmware ABI and has no equivalent
representation in the generic nl80211 TX power interface. Therefore,
the payload is carried as an opaque binary attribute through a vendor
command.

The driver forwards the firmware-defined payload between user space and
the firmware without interpreting its contents.

Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
 drivers/net/wireless/nxp/nxpwifi/Makefile   |   1 +
 drivers/net/wireless/nxp/nxpwifi/cfg80211.c |   5 +-
 drivers/net/wireless/nxp/nxpwifi/main.h     |   1 +
 drivers/net/wireless/nxp/nxpwifi/vendor.c   | 164 ++++++++++++++++++++
 drivers/net/wireless/nxp/nxpwifi/vendor.h   |  46 ++++++
 5 files changed, 216 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/wireless/nxp/nxpwifi/vendor.c
 create mode 100644 drivers/net/wireless/nxp/nxpwifi/vendor.h

diff --git a/drivers/net/wireless/nxp/nxpwifi/Makefile b/drivers/net/wireless/nxp/nxpwifi/Makefile
index 8f581429f28d..f82435253a72 100644
--- a/drivers/net/wireless/nxp/nxpwifi/Makefile
+++ b/drivers/net/wireless/nxp/nxpwifi/Makefile
@@ -28,6 +28,7 @@ nxpwifi-y += sta_tx.o
 nxpwifi-y += sta_rx.o
 nxpwifi-y += uap_txrx.o
 nxpwifi-y += cfg80211.o
+nxpwifi-y += vendor.o
 nxpwifi-y += ethtool.o
 nxpwifi-y += 11h.o
 nxpwifi-$(CONFIG_DEBUG_FS) += debugfs.o
diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c
index 8805439c0b4b..16b970227e9c 100644
--- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c
+++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c
@@ -10,6 +10,7 @@
 #include "cmdevt.h"
 #include "11n.h"
 #include "wmm.h"
+#include "vendor.h"
 
 static const struct ieee80211_iface_limit nxpwifi_ap_sta_limits[] = {
 	{
@@ -91,7 +92,7 @@ u8 nxpwifi_get_chan_type(struct nxpwifi_private *priv)
 }
 
 /* Retrieve the driver private data from the wiphy */
-static void *nxpwifi_cfg80211_get_adapter(struct wiphy *wiphy)
+void *nxpwifi_cfg80211_get_adapter(struct wiphy *wiphy)
 {
 	return (void *)(*(unsigned long *)wiphy_priv(wiphy));
 }
@@ -3834,6 +3835,8 @@ int nxpwifi_register_cfg80211(struct nxpwifi_adapter *adapter)
 	wiphy->cipher_suites = nxpwifi_cipher_suites;
 	wiphy->n_cipher_suites = ARRAY_SIZE(nxpwifi_cipher_suites);
 
+	nxpwifi_set_vendor_commands(wiphy);
+
 	if (adapter->regd) {
 		wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG |
 					   REGULATORY_DISABLE_BEACON_HINTS |
diff --git a/drivers/net/wireless/nxp/nxpwifi/main.h b/drivers/net/wireless/nxp/nxpwifi/main.h
index 9ae017bdc6ca..bbfc4266c48d 100644
--- a/drivers/net/wireless/nxp/nxpwifi/main.h
+++ b/drivers/net/wireless/nxp/nxpwifi/main.h
@@ -1435,4 +1435,5 @@ void nxpwifi_dev_debugfs_remove(struct nxpwifi_private *priv);
 int nxpwifi_reinit_sw(struct nxpwifi_adapter *adapter);
 void nxpwifi_shutdown_sw(struct nxpwifi_adapter *adapter);
 bool nxpwifi_is_valid_region_code(enum nxpwifi_region_code code);
+void *nxpwifi_cfg80211_get_adapter(struct wiphy *wiphy);
 #endif /* !_NXPWIFI_MAIN_H_ */
diff --git a/drivers/net/wireless/nxp/nxpwifi/vendor.c b/drivers/net/wireless/nxp/nxpwifi/vendor.c
new file mode 100644
index 000000000000..11cc2d123d3f
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/vendor.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * NXP Wireless LAN device driver: vendor command interface
+ *
+ * Copyright 2011-2026 NXP
+ */
+
+#include <net/mac80211.h>
+#include <net/netlink.h>
+#include "vendor.h"
+#include "main.h"
+
+static const struct nla_policy
+nxpwifi_txpwr_policy[NXPWIFI_ATTR_TXPWR_MAX + 1] = {
+	[NXPWIFI_ATTR_TXPWR_ACTION]  = NLA_POLICY_MAX(NLA_U8,
+						      NXPWIFI_TXPWR_ACTION_SET),
+	[NXPWIFI_ATTR_TXPWR_SUBBAND] = { .type = NLA_U8 },
+	[NXPWIFI_ATTR_TXPWR_DATA]    = { .type = NLA_BINARY,
+					 .len  = NXPWIFI_MAX_TRPC_BUF },
+};
+
+/* The subband selector is not a contiguous range: 0x00 selects the 2.4 GHz
+ * band and 0x10..0x13 select the four 5 GHz subbands. Validate it explicitly
+ * instead of relying on a simple min/max policy bound.
+ */
+static bool nxpwifi_txpwr_subband_valid(u8 subband)
+{
+	switch (subband) {
+	case NXPWIFI_TXPWR_SUBBAND_2G:
+	case NXPWIFI_TXPWR_SUBBAND_5G_0:
+	case NXPWIFI_TXPWR_SUBBAND_5G_1:
+	case NXPWIFI_TXPWR_SUBBAND_5G_2:
+	case NXPWIFI_TXPWR_SUBBAND_5G_3:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static int nxpwifi_vendor_txpower_limit(struct wiphy *wiphy,
+					struct wireless_dev *wdev,
+					const void *data, int data_len)
+{
+	struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy);
+	struct nlattr *tb[NXPWIFI_ATTR_TXPWR_MAX + 1];
+	struct nxpwifi_ds_chan_trpc_cfg *ch_trpc;
+	struct nxpwifi_private *priv;
+	struct sk_buff *resp;
+	u8 action, subband = 0;
+	u16 tlv_len = 0;
+	int ret;
+
+	ret = nla_parse(tb, NXPWIFI_ATTR_TXPWR_MAX, data, data_len,
+			nxpwifi_txpwr_policy, NULL);
+	if (ret)
+		return ret;
+
+	if (!tb[NXPWIFI_ATTR_TXPWR_ACTION])
+		return -EINVAL;
+	action = nla_get_u8(tb[NXPWIFI_ATTR_TXPWR_ACTION]);
+
+	if (tb[NXPWIFI_ATTR_TXPWR_SUBBAND]) {
+		subband = nla_get_u8(tb[NXPWIFI_ATTR_TXPWR_SUBBAND]);
+		if (!nxpwifi_txpwr_subband_valid(subband))
+			return -EINVAL;
+	} else if (action == NXPWIFI_TXPWR_ACTION_GET) {
+		/* A GET selects which per-subband table to read back, so the
+		 * subband selector is mandatory for a GET.
+		 */
+		return -EINVAL;
+	}
+
+	if (action == NXPWIFI_TXPWR_ACTION_SET) {
+		if (!tb[NXPWIFI_ATTR_TXPWR_DATA])
+			return -EINVAL;
+		tlv_len = nla_len(tb[NXPWIFI_ATTR_TXPWR_DATA]);
+	}
+
+	priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA);
+	if (!priv)
+		return -ENODEV;
+
+	/* A SET only needs room for the supplied TLVs; a GET needs room for
+	 * the firmware to write back the full per-subband table.
+	 */
+	ch_trpc = kzalloc_flex(*ch_trpc, tlvbuffer,
+			       (action == NXPWIFI_TXPWR_ACTION_SET) ?
+			       tlv_len : NXPWIFI_MAX_TRPC_BUF, GFP_KERNEL);
+	if (!ch_trpc)
+		return -ENOMEM;
+
+	ch_trpc->action = (action == NXPWIFI_TXPWR_ACTION_SET) ?
+			  HOST_ACT_GEN_SET : HOST_ACT_GEN_GET;
+	ch_trpc->subband = subband;
+	if (tlv_len)
+		memcpy(ch_trpc->tlvbuffer,
+		       nla_data(tb[NXPWIFI_ATTR_TXPWR_DATA]), tlv_len);
+	/* Firmware payload is action + subband followed by the TLV table. */
+	ch_trpc->len = sizeof(ch_trpc->action) + sizeof(ch_trpc->subband) +
+		       tlv_len;
+
+	ret = nxpwifi_chan_trpc_cfg(priv, NXPWIFI_SYNC_CMD, ch_trpc);
+	if (ret)
+		goto done;
+
+	if (action == NXPWIFI_TXPWR_ACTION_GET) {
+		u16 hdr_len = sizeof(ch_trpc->action) +
+			      sizeof(ch_trpc->subband);
+		u16 resp_len, skb_len;
+
+		/* Guard against a malformed firmware response before using
+		 * ch_trpc->len to size the reply.
+		 */
+		if (ch_trpc->len < hdr_len ||
+		    ch_trpc->len > hdr_len + NXPWIFI_MAX_TRPC_BUF) {
+			ret = -EIO;
+			goto done;
+		}
+		resp_len = ch_trpc->len - hdr_len;
+		skb_len = nla_total_size(resp_len);
+
+		resp = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, skb_len);
+		if (!resp) {
+			ret = -ENOMEM;
+			goto done;
+		}
+
+		if (nla_put(resp, NXPWIFI_ATTR_TXPWR_DATA, resp_len,
+			    ch_trpc->tlvbuffer)) {
+			kfree_skb(resp);
+			ret = -ENOBUFS;
+			goto done;
+		}
+
+		ret = cfg80211_vendor_cmd_reply(resp);
+	}
+
+done:
+	kfree(ch_trpc);
+	return ret;
+}
+
+static const struct wiphy_vendor_command nxpwifi_vendor_commands[] = {
+	{
+		.info = {
+			.vendor_id = NXP_OUI,
+			.subcmd = NXPWIFI_VENDOR_CMD_TXPOWER_LIMIT,
+		},
+		/* TX power limits are a configuration/calibration setting and
+		 * can be programmed while the interface is down, so no
+		 * WIPHY_VENDOR_CMD_NEED_RUNNING flag is required.
+		 */
+		.flags = 0,
+		.doit = nxpwifi_vendor_txpower_limit,
+		.policy = nxpwifi_txpwr_policy,
+		.maxattr = NXPWIFI_ATTR_TXPWR_MAX,
+	},
+};
+
+void nxpwifi_set_vendor_commands(struct wiphy *wiphy)
+{
+	wiphy->vendor_commands = nxpwifi_vendor_commands;
+	wiphy->n_vendor_commands = ARRAY_SIZE(nxpwifi_vendor_commands);
+}
diff --git a/drivers/net/wireless/nxp/nxpwifi/vendor.h b/drivers/net/wireless/nxp/nxpwifi/vendor.h
new file mode 100644
index 000000000000..f962ea520af0
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/vendor.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * NXP Wireless LAN device driver: vendor command interface
+ *
+ * Copyright 2011-2026 NXP
+ */
+
+#ifndef __NXPWIFI_VENDOR_H__
+#define __NXPWIFI_VENDOR_H__
+
+#define NXP_OUI	0x006037
+
+enum nxpwifi_vendor_commands {
+	NXPWIFI_VENDOR_CMD_TXPOWER_LIMIT,
+};
+
+/* Actions for NXPWIFI_ATTR_TXPWR_ACTION. */
+enum nxpwifi_txpwr_action {
+	NXPWIFI_TXPWR_ACTION_GET,
+	NXPWIFI_TXPWR_ACTION_SET,
+};
+
+/* Subband selectors for NXPWIFI_ATTR_TXPWR_SUBBAND: 0x00 is the 2.4 GHz
+ * band and 0x10..0x13 are the four 5 GHz subbands.
+ */
+enum nxpwifi_txpwr_subband {
+	NXPWIFI_TXPWR_SUBBAND_2G	= 0x00,
+	NXPWIFI_TXPWR_SUBBAND_5G_0	= 0x10,
+	NXPWIFI_TXPWR_SUBBAND_5G_1	= 0x11,
+	NXPWIFI_TXPWR_SUBBAND_5G_2	= 0x12,
+	NXPWIFI_TXPWR_SUBBAND_5G_3	= 0x13,
+};
+
+/* Attributes for NXPWIFI_VENDOR_CMD_TXPOWER_LIMIT. */
+enum nxpwifi_txpwr_attrs {
+	NXPWIFI_ATTR_TXPWR_UNSPEC,
+	NXPWIFI_ATTR_TXPWR_ACTION,	/* u8, enum nxpwifi_txpwr_action */
+	NXPWIFI_ATTR_TXPWR_SUBBAND,	/* u8, subband selector for GET */
+	NXPWIFI_ATTR_TXPWR_DATA,	/* binary, per-subband TRPC TLVs */
+
+	__NXPWIFI_ATTR_TXPWR_AFTER_LAST,
+	NXPWIFI_ATTR_TXPWR_MAX = __NXPWIFI_ATTR_TXPWR_AFTER_LAST - 1
+};
+
+void nxpwifi_set_vendor_commands(struct wiphy *wiphy);
+#endif /* __NXPWIFI_VENDOR_H__ */
-- 
2.34.1


  parent reply	other threads:[~2026-10-01 10:07 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-01 10:06 [PATCH 0/2] wifi: nxpwifi: add TX power limit configuration support jeff.chen_1
2026-10-01 10:06 ` [PATCH 1/2] wifi: nxpwifi: add TX power limit host command support jeff.chen_1
2026-10-01 10:08   ` Johannes Berg
2026-10-01 10:06 ` jeff.chen_1 [this message]
2026-10-01 10:10   ` [PATCH 2/2] wifi: nxpwifi: add netlink vendor command for TX power limits Johannes Berg

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261001100640.2829946-3-jeff.chen_1@oss.nxp.com \
    --to=jeff.chen_1@oss.nxp.com \
    --cc=francesco@dolcini.it \
    --cc=jeff.chen_1@nxp.com \
    --cc=johannes@sipsolutions.net \
    --cc=linux-wireless@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox