wpa_supplicant_8_lib: Add support for OPM mode configuration

Add support to configure OPM mode with SET_PS_CONFIG command
Command: "SET_PS_CONFIG <opm_mode> <ps_ito> <spec_wake>"

opm_mode: Optimized Power Management Mode
ps_ito: Power Save Inactivity Timeout
spec_wake: Speculative wake interval

Examples:
1. Disable OPM: "SET_PS_CONFIG 0"
2. Enable OPM: "SET_PS_CONFIG 1"
3. User defined OPM: "SET_PS_CONFIG 2 50 0"

Change-Id: Idc09fb84bb79785459f73a52bee58a79e831c86d
CRs-Fixed: 3578543
diff --git a/qcwcn/wpa_supplicant_8_lib/driver_cmd_nl80211.c b/qcwcn/wpa_supplicant_8_lib/driver_cmd_nl80211.c
index 970e521..1d02769 100644
--- a/qcwcn/wpa_supplicant_8_lib/driver_cmd_nl80211.c
+++ b/qcwcn/wpa_supplicant_8_lib/driver_cmd_nl80211.c
@@ -179,6 +179,10 @@
 #define TWT_RESUME_RESP_LEN      strlen(TWT_RESUME_RESP)
 #define TWT_NOTIFY_RESP_LEN      strlen(TWT_NOTIFY_RESP)
 
+#define OPM_MODE_DISABLE         0
+#define OPM_MODE_ENABLE          1
+#define OPM_MODE_USER_DEFINED    2
+
 static int twt_async_support = -1;
 
 struct twt_setup_parameters {
@@ -2750,6 +2754,20 @@
 	return val;
 }
 
+static u16 get_u16_from_string(char *cmd_string, int *ret)
+{
+	u16 val = 0;
+
+	*ret = 0;
+	errno = 0;
+	val = strtol(cmd_string, NULL, 10) & 0xFFFF;
+	if (errno == ERANGE || (errno != 0 && val == 0)) {
+		wpa_printf(MSG_ERROR, "Invalid input to get u16 value");
+		*ret = -EINVAL;
+	}
+	return val;
+}
+
 char *move_to_next_str(char *cmd)
 {
 	if (*cmd == '\0')
@@ -6402,6 +6420,116 @@
 	return ret;
 }
 
+static uint8_t wpa_driver_convert_opm_mode(uint8_t opm_mode)
+{
+	switch (opm_mode) {
+	case 0:
+		return QCA_WLAN_VENDOR_OPM_MODE_DISABLE;
+	case 1:
+		return QCA_WLAN_VENDOR_OPM_MODE_ENABLE;
+	case 2:
+		return QCA_WLAN_VENDOR_OPM_MODE_USER_DEFINED;
+	default:
+		return opm_mode;
+	}
+}
+
+static int wpa_driver_ps_config_cmd(struct i802_bss *bss, char *cmd)
+{
+	struct wpa_driver_nl80211_data *drv;
+	struct nl_msg *nlmsg;
+	struct nlattr *attr;
+	u8 opm_mode;
+	u16 ps_ito, spec_wake;
+	int ret;
+
+	drv = bss->drv;
+	cmd = skip_white_space(cmd);
+	if (*cmd == '\0') {
+		wpa_printf(MSG_ERROR, "mode and config values are missing");
+		return -EINVAL;
+	}
+	opm_mode = get_u8_from_string(cmd, &ret);
+	if (ret < 0) {
+		wpa_printf(MSG_ERROR, "ps_config: Invalid opm_mode");
+		return -EINVAL;
+	}
+
+	if (opm_mode == OPM_MODE_USER_DEFINED) {
+		cmd = move_to_next_str(cmd);
+		if (*cmd == '\0') {
+			wpa_printf(MSG_ERROR, "ps_ito is missing in command");
+			return -EINVAL;
+		}
+		ps_ito = get_u16_from_string(cmd, &ret);
+		if (ret < 0) {
+			wpa_printf(MSG_ERROR, "Invalid ps_ito value");
+			return -EINVAL;
+		}
+		cmd = move_to_next_str(cmd);
+		if (*cmd == '\0') {
+			wpa_printf(MSG_ERROR,
+				   "spec_wake is missing in command");
+			return -EINVAL;
+		}
+		spec_wake = get_u16_from_string(cmd, &ret);
+		if (ret < 0) {
+			wpa_printf(MSG_ERROR, "Invalid spec_wake value");
+			return -EINVAL;
+		}
+	}
+
+	nlmsg = prepare_vendor_nlmsg(drv, bss->ifname,
+				     QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION);
+	if (!nlmsg) {
+		wpa_printf(MSG_ERROR,
+			   "Failed to allocate nlmsg for set_opm_mode cmd");
+		return -ENOMEM;
+	}
+
+	attr = nla_nest_start(nlmsg, NL80211_ATTR_VENDOR_DATA);
+	if (!attr) {
+		ret = -ENOMEM;
+		wpa_printf(MSG_ERROR,
+			   "Failed to create nl attr for set_opm_mode cmd");
+		goto nlmsg_fail;
+	}
+	if (nla_put_u8(nlmsg,
+		       QCA_WLAN_VENDOR_ATTR_CONFIG_OPTIMIZED_POWER_MANAGEMENT,
+		       wpa_driver_convert_opm_mode(opm_mode))) {
+		ret = -ENOMEM;
+		wpa_printf(MSG_ERROR, "Failed to put power_save_mode value");
+		goto nlmsg_fail;
+	}
+	if (opm_mode == OPM_MODE_USER_DEFINED) {
+		if (nla_put_u16(nlmsg, QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_ITO,
+				ps_ito)) {
+			ret = -ENOMEM;
+			wpa_printf(MSG_ERROR, "Failed to put ps_ito value");
+			goto nlmsg_fail;
+		}
+		if (nla_put_u16(nlmsg,
+				QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_SPEC_WAKE_INTERVAL,
+				spec_wake)) {
+			ret = -ENOMEM;
+			wpa_printf(MSG_ERROR, "Failed to put spec_wake value");
+			goto nlmsg_fail;
+		}
+	}
+	nla_nest_end(nlmsg, attr);
+
+	ret = send_nlmsg((struct nl_sock *)drv->global->nl, nlmsg, NULL, NULL);
+	if (ret) {
+		wpa_printf(MSG_ERROR,
+			   "Failed to send set_opm_mode nlmsg, error:%d", ret);
+		return ret;
+	}
+	return 0;
+nlmsg_fail:
+	nlmsg_free(nlmsg);
+	return ret;
+}
+
 int wpa_driver_nl80211_driver_cmd(void *priv, char *cmd, char *buf,
 				  size_t buf_len )
 {
@@ -6751,6 +6879,17 @@
 		 */
 		cmd += 17;
 		return wpa_driver_set_ul_mu_cfg(bss, cmd);
+	} else if (os_strncasecmp(cmd, "SET_PS_CONFIG ", 14) == 0) {
+		/* DRIVER SET_PS_CONFIG <opm_mode> <ps_ito> <spec_wake>
+		 * opm_mode  - Optimized power management Mode
+		 *     value 0 - Disable OPM
+		 *     value 1 - Enable OPM
+		 *     value 2 - User defined OPM
+		 * ps_ito    - Power save inactivity timeout
+		 * spec_wake - Speculative wake interval
+		 */
+		cmd += 14;
+		return wpa_driver_ps_config_cmd(bss, cmd);
 	} else { /* Use private command */
 		memset(&ifr, 0, sizeof(ifr));
 		memset(&priv_cmd, 0, sizeof(priv_cmd));