From 7867b89f17ad995dd63d2fe60134a9e757af3859 Mon Sep 17 00:00:00 2001
From: Kshitij Bisht <kshitij.bisht@infineon.com>
Date: Thu, 25 Apr 2024 15:41:44 -0500
Subject: [PATCH 256/301] non-upstream: vendor cmds string infrastructure with
 offload_config implementation

IW Vendor Command String passing infrastructure is being added in this
wherein fmac will handle user supplied strings directly via iw vendor
interface. This can be used for migration of any wl cmd to iw wherein
parameters passed to wl remain same is for iw.

Example : wl -> iw

1. wl offload_config Profile LowPwr 1 -s 0x3df
   echo 'offload_config Profile LowPwr 1 -s 0x3df ' | iw dev wlan0 vendor
   send 0x000319 0x1C -

2. wl offload_config Enable 1
   echo 'offload_config Enable 1 ' | iw dev wlan0 vendor send 0x000319
   0x1C -

Signed-off-by: Kshitij Bisht <kshitij.bisht@infineon.com>
---
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 6c466aadb..b9247a674 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -10659,6 +10659,11 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,
 		}
 	}
 
+	err = ifx_vndr_cmdstr_hashtbl_init();
+	if (err) {
+		bphy_err(drvr, "vendor_cmd_hashtable initialisation failed (%d)\n", err);
+		goto detach;
+	}
 	/* (re-) activate FWEH event handling */
 	err = brcmf_fweh_activate_events(ifp);
 	if (err) {
@@ -10699,6 +10704,7 @@ void brcmf_cfg80211_detach(struct brcmf_cfg80211_info *cfg)
 	if (!cfg)
 		return;
 
+	ifx_vndr_cmdstr_hashtbl_deinit();
 	brcmf_pno_detach(cfg);
 	brcmf_btcoex_detach(cfg);
 	wiphy_unregister(cfg->wiphy);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
index 76dafc90b..9afe7a09f 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
@@ -518,6 +518,8 @@ void brcmf_abort_scanning(struct brcmf_cfg80211_info *cfg);
 void brcmf_cfg80211_free_netdev(struct net_device *ndev);
 bool brcmf_is_apmode_operating(struct wiphy *wiphy);
 void brcmf_cfg80211_update_proto_addr_mode(struct wireless_dev *wdev);
+int ifx_vndr_cmdstr_hashtbl_init(void);
+void ifx_vndr_cmdstr_hashtbl_deinit(void);
 
 int brcmf_set_wsec(struct brcmf_if *ifp, const u8 *key, u16 key_len, u16 flags);
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
index ab4a8d4df..05fa1a841 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
@@ -123,7 +123,8 @@ unsigned int brcmf_offload_feat = BRCMF_OL_ARP |
 				  BRCMF_OL_DLTRO |
 				  BRCMF_OL_PNO |
 				  BRCMF_OL_KEEPALIVE |
-				  BRCMF_OL_GTKOE;
+				  BRCMF_OL_GTKOE |
+				  BRCMF_OL_WOWLPF;
 module_param_named(offload_feat, brcmf_offload_feat, uint, 0400);
 MODULE_PARM_DESC(offload_feat,
 		 "Offload feat bitmap: 0:arp 1:nd 2:mdns 3:icmp 4:tcp-keepalive "
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c
index 853d0c383..03085634e 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c
@@ -350,6 +350,13 @@ const struct wiphy_vendor_command brcmf_vendor_cmds[] = {
 			   VENDOR_CMD_RAW_DATA,
 			   ifx_cfg80211_vndr_cmds_wnm_wl_cap)
 	},
+	{
+		IFX_SUBCMD(CMDSTR,
+			   (WIPHY_VENDOR_CMD_NEED_WDEV |
+				WIPHY_VENDOR_CMD_NEED_NETDEV),
+			VENDOR_CMD_RAW_DATA,
+			ifx_cfg80211_vndr_cmds_str)
+	},
 };
 
 const struct nl80211_vendor_cmd_info brcmf_vendor_events[] = {
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.c
index a44f14792..6755167d0 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.c
@@ -41,6 +41,18 @@
 #include "xtlv.h"
 #include "twt.h"
 #include "bus.h"
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/jhash.h>
+#include <linux/hashtable.h>
+#include "common.h"
+
+static const struct ifx_vendor_cmdstr ifx_vndr_cmdstr[] = {
+	{ "offload_config", ifx_vndr_cmdstr_offload_config},
+	{ NULL, NULL }
+};
+
+DEFINE_HASHTABLE(vndr_cmd_hashtbl, VNDR_CMD_HASH_BITS);
 
 static int ifx_cfg80211_vndr_send_cmd_reply(struct wiphy *wiphy,
 					    const void  *data, int len)
@@ -59,6 +71,45 @@ static int ifx_cfg80211_vndr_send_cmd_reply(struct wiphy *wiphy,
 	return cfg80211_vendor_cmd_reply(skb);
 }
 
+int ifx_vndr_cmdstr_hashtbl_init(void)
+{
+	int i;
+	u32 jhash_key;
+
+	brcmf_dbg(TRACE, "enter\n");
+
+	hash_init(vndr_cmd_hashtbl);
+
+	/* Initializing the VENDOR CMD hashtable with all the string commmands
+	 * and func_handler in ifx_vndr_str_cmds
+	 */
+	for (i = 0; ifx_vndr_cmdstr[i].name; i++) {
+		struct ifx_vndr_cmdstr_hashtbl *vndr_hashtbl;
+
+		vndr_hashtbl = kzalloc(sizeof(*vndr_hashtbl), GFP_KERNEL);
+		if (!vndr_hashtbl)
+			return -ENOMEM;
+
+		vndr_hashtbl->vndr_cmd_addr = (struct ifx_vendor_cmdstr *)&ifx_vndr_cmdstr[i];
+		jhash_key = jhash(ifx_vndr_cmdstr[i].name, strlen(ifx_vndr_cmdstr[i].name), 0);
+		hash_add(vndr_cmd_hashtbl, &vndr_hashtbl->node, jhash_key);
+	}
+
+	return 0;
+}
+
+void ifx_vndr_cmdstr_hashtbl_deinit(void)
+{
+	struct ifx_vndr_cmdstr_hashtbl *vndr_hashtbl;
+	struct hlist_node *tmp_node;
+	int i;
+
+	hash_for_each_safe(vndr_cmd_hashtbl, i, tmp_node, vndr_hashtbl, node) {
+		hash_del(&vndr_hashtbl->node);
+		kfree(vndr_hashtbl);
+	}
+}
+
 static void
 ifx_cfgvendor_twt_parse_params(const struct nlattr *attr_iter,
 			       struct brcmf_twt_params *twt_params)
@@ -775,3 +826,124 @@ int ifx_cfg80211_vndr_cmds_wnm_wl_cap(struct wiphy *wiphy,
 
 	return ret;
 }
+
+int ifx_vndr_cmdstr_offload_config(struct wiphy *wiphy, struct wireless_dev *wdev,
+				   char cmd_str[VNDR_CMD_STR_NUM][VNDR_CMD_STR_MAX_LEN],
+				   long cmd_val[VNDR_CMD_VAL_NUM])
+{
+	struct brcmf_cfg80211_vif *vif;
+	struct brcmf_if *ifp;
+	int ret = 0;
+
+	vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev);
+	ifp = vif->ifp;
+
+	/* IW CMDSTR TEMPLATE.
+	 * echo 'offload_config Enable 1 ' | iw dev wlan0 vendor send 0x000319
+	 * 0x1C -
+	 *
+	 * echo 'offload_config Profile LowPwr 1 -s 0x3df ' | iw dev wlan0 vendor
+	 * send 0x000319 0x1C -
+	 *
+	 */
+	if (cmd_str[1] && (strlen(cmd_str[1]) == 6) &&
+	    (memcmp(cmd_str[1], "Enable", 6)) == 0 &&
+	    (cmd_val[0] == 0 || cmd_val[0] == 1)) {
+		brcmf_generic_offload_enable(ifp, brcmf_offload_feat, cmd_val[0]);
+	} else if (cmd_str[1] && (strlen(cmd_str[1]) == 7) &&
+		  (memcmp(cmd_str[1], "Profile", 7)) == 0) {
+		if (cmd_str[2]) {
+			unsigned int ol_prof;
+
+			if ((strlen(cmd_str[2]) == 6) &&
+			    (memcmp(cmd_str[2], "LowPwr", 6)) == 0) {
+				ol_prof = BRCMF_OL_PROF_TYPE_LOW_PWR;
+			} else if ((strlen(cmd_str[2]) == 6) &&
+				 (memcmp(cmd_str[2], "MidPwr", 6)) == 0) {
+				ol_prof = BRCMF_OL_PROF_TYPE_MID_PWR;
+			} else if ((strlen(cmd_str[2]) == 7) &&
+				 (memcmp(cmd_str[2], "HighPwr", 7)) == 0) {
+				ol_prof = BRCMF_OL_PROF_TYPE_HIGH_PWR;
+			} else {
+				brcmf_err("unknown offload_config Profile attr\n");
+				return -EINVAL;
+			}
+			if (cmd_str[3] && (strlen(cmd_str[3]) == 2) &&
+			    (memcmp(cmd_str[3], "-s", 2)) == 0)
+				brcmf_generic_offload_config(ifp, ~cmd_val[1], ol_prof, cmd_val[0]);
+			else
+				brcmf_generic_offload_config(ifp, brcmf_offload_feat, ol_prof,
+							     cmd_val[0]);
+		} else {
+			brcmf_err("unknown offload_config Profile attr\n");
+			return -EINVAL;
+		}
+	} else {
+		brcmf_err("unknown offload_config attr\n");
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+int ifx_cfg80211_vndr_cmds_str(struct wiphy *wiphy, struct wireless_dev *wdev,
+			       const void *data, int len)
+{
+	struct brcmf_cfg80211_vif *vif;
+	struct brcmf_if *ifp;
+	struct ifx_vndr_cmdstr_hashtbl *hash_entry;
+	u32 jhash_key;
+	int ret = 0, i = 0, j = 0;
+	unsigned long val;
+	char cmd_str[VNDR_CMD_STR_NUM][VNDR_CMD_STR_MAX_LEN] = {{""}};
+	long cmd_val[VNDR_CMD_VAL_NUM];
+	char *tok = NULL, *buf = NULL;
+
+	buf = (char *)data;
+	vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev);
+	ifp = vif->ifp;
+
+	memset(cmd_val, -1, VNDR_CMD_VAL_NUM * sizeof(*cmd_val));
+
+	while ((tok = strsep(&buf, " ")) != NULL) {
+		if (kstrtoul(tok, 10, &val) == 0) {
+			cmd_val[j] = val;
+			j++;
+		} else if ((strncmp(tok, "0x", 2) == 0) || (strncmp(tok, "0X", 2) == 0)) {
+			if (kstrtoul(tok, 16, &val) == 0) {
+				cmd_val[j] = val;
+				j++;
+			} else {
+				brcmf_err("Failed to parse hex token\n");
+				return -EINVAL;
+			}
+		} else if (strnlen(tok, VNDR_CMD_STR_MAX_LEN) <= VNDR_CMD_STR_MAX_LEN) {
+			strncpy(cmd_str[i], tok, strnlen(tok, VNDR_CMD_STR_MAX_LEN));
+			i++;
+		} else {
+			brcmf_err("Failed to parse token\n");
+			return -EINVAL;
+		}
+	}
+
+	/* Run the user cmd string input via Jenkins hash to pass and search the entry in
+	 * vendor cmd hashtable initialized at load time.
+	 */
+	jhash_key = jhash(cmd_str[0], strlen(cmd_str[0]), 0);
+
+	/* Search the user entered vndr cmd entry in the hash table and call its corresponding
+	 * function handler.
+	 */
+	hash_for_each_possible(vndr_cmd_hashtbl, hash_entry, node, jhash_key) {
+		if (hash_entry->vndr_cmd_addr &&
+		    (strlen(cmd_str[0]) == strlen(hash_entry->vndr_cmd_addr->name)) &&
+		    memcmp(hash_entry->vndr_cmd_addr->name, cmd_str[0],
+			   strlen(hash_entry->vndr_cmd_addr->name)) == 0) {
+			ret = hash_entry->vndr_cmd_addr->func(wiphy, wdev,
+					cmd_str, cmd_val);
+			break;
+		}
+	}
+
+	return ret;
+}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.h
index 944720fdd..fdf6546a6 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.h
@@ -111,6 +111,10 @@ struct bcm_iov_buf {
  *
  * @IFX_VENDOR_SCMD_HWCAPS: Get device's capability.
  *
+ * @IFX_VENDOR_SCMD_CMDSTR: New vendor string infra subcmd to handle user supplied strings.
+ *	String parsing and calling corresponding function handler for a specific command
+ *	given by user.
+ *
  * @IFX_VENDOR_SCMD_MAX: This acts as a the tail of cmds list.
  *      Make sure it located at the end of the list.
  */
@@ -143,7 +147,8 @@ enum ifx_nl80211_vendor_subcmds {
 	SCMD(WNM)		= 25,
 	SCMD(HWCAPS)		= 26,
 	SCMD(WNM_WL_CAP)	= 27,
-	SCMD(MAX)		= 28
+	SCMD(CMDSTR)		= 28,
+	SCMD(MAX)		= 29
 };
 
 /*
@@ -697,6 +702,25 @@ struct ifx_maxidle_wnm {
 	int protect;
 };
 
+/* String based vendor commands infra
+ */
+#define VNDR_CMD_STR_NUM	15
+#define VNDR_CMD_STR_MAX_LEN	20
+#define VNDR_CMD_VAL_NUM	15
+#define VNDR_CMD_HASH_BITS	4
+
+struct ifx_vendor_cmdstr {
+	const char *name;
+	int (*func)(struct wiphy *wiphy, struct wireless_dev *wdev,
+		    char cmd_str[VNDR_CMD_STR_NUM][VNDR_CMD_STR_MAX_LEN],
+		    long cmd_val[VNDR_CMD_VAL_NUM]);
+};
+
+struct ifx_vndr_cmdstr_hashtbl {
+	struct ifx_vendor_cmdstr *vndr_cmd_addr;
+	struct hlist_node node;
+};
+
 int ifx_cfg80211_vndr_cmds_twt(struct wiphy *wiphy,
 			       struct wireless_dev *wdev, const void  *data, int len);
 int ifx_cfg80211_vndr_cmds_bsscolor(struct wiphy *wiphy,
@@ -735,6 +759,11 @@ int ifx_cfg80211_vndr_cmds_hwcaps(struct wiphy *wiphy,
 int ifx_cfg80211_vndr_cmds_wnm_wl_cap(struct wiphy *wiphy,
 				      struct wireless_dev *wdev,
 				      const void *data, int len);
+int ifx_vndr_cmdstr_offload_config(struct wiphy *wiphy, struct wireless_dev *wdev,
+				   char cmd_str[VNDR_CMD_STR_NUM][VNDR_CMD_STR_MAX_LEN],
+				   long cmd_val[VNDR_CMD_VAL_NUM]);
+int ifx_cfg80211_vndr_cmds_str(struct wiphy *wiphy, struct wireless_dev *wdev,
+			       const void *data, int len);
 
 #endif /* IFX_VENDOR_H */
 
