qcacld-3.0: Add support for get and set OEM capability

Add support for get and set OEM capability over private netlink
socket.

Change-Id: I8c4a8c19633845750ec9d2492632471c68ba410d
CRs-Fixed: 949469
diff --git a/core/hdd/src/wlan_hdd_oemdata.c b/core/hdd/src/wlan_hdd_oemdata.c
index e23412a..52c74fe 100644
--- a/core/hdd/src/wlan_hdd_oemdata.c
+++ b/core/hdd/src/wlan_hdd_oemdata.c
@@ -44,10 +44,82 @@
 #include "qwlan_version.h"
 #include "cds_utils.h"
 #include "wma.h"
+#include "sme_api.h"
 
 static struct hdd_context_s *p_hdd_ctx;
 
 /**
+ * populate_oem_data_cap() - populate oem capabilities
+ * @adapter: device adapter
+ * @data_cap: pointer to populate the capabilities
+ *
+ * Return: error code
+ */
+static int populate_oem_data_cap(hdd_adapter_t *adapter,
+				 t_iw_oem_data_cap *data_cap)
+{
+	CDF_STATUS status = CDF_STATUS_E_FAILURE;
+	struct hdd_config *config;
+	uint32_t num_chan;
+	uint8_t *chan_list;
+	hdd_context_t *hdd_ctx = adapter->pHddCtx;
+
+	config = hdd_ctx->config;
+	if (!config) {
+		hdd_err("HDD configuration is null");
+		return -EINVAL;
+	}
+	chan_list = cdf_mem_malloc(sizeof(uint8_t) * OEM_CAP_MAX_NUM_CHANNELS);
+	if (NULL == chan_list) {
+		hdd_err("Memory allocation failed");
+		return -ENOMEM;
+	}
+
+	strlcpy(data_cap->oem_target_signature, OEM_TARGET_SIGNATURE,
+		OEM_TARGET_SIGNATURE_LEN);
+	data_cap->oem_target_type = hdd_ctx->target_type;
+	data_cap->oem_fw_version = hdd_ctx->target_fw_version;
+	data_cap->driver_version.major = QWLAN_VERSION_MAJOR;
+	data_cap->driver_version.minor = QWLAN_VERSION_MINOR;
+	data_cap->driver_version.patch = QWLAN_VERSION_PATCH;
+	data_cap->driver_version.build = QWLAN_VERSION_BUILD;
+	data_cap->allowed_dwell_time_min = config->nNeighborScanMinChanTime;
+	data_cap->allowed_dwell_time_max = config->nNeighborScanMaxChanTime;
+	data_cap->curr_dwell_time_min =
+		sme_get_neighbor_scan_min_chan_time(hdd_ctx->hHal,
+						    adapter->sessionId);
+	data_cap->curr_dwell_time_max =
+		sme_get_neighbor_scan_max_chan_time(hdd_ctx->hHal,
+						    adapter->sessionId);
+	data_cap->supported_bands = config->nBandCapability;
+
+	/* request for max num of channels */
+	num_chan = WNI_CFG_VALID_CHANNEL_LIST_LEN;
+	status = sme_get_cfg_valid_channels(hdd_ctx->hHal,
+					    &chan_list[0], &num_chan);
+	if (CDF_STATUS_SUCCESS != status) {
+		hdd_err("failed to get valid channel list, status: %d", status);
+		cdf_mem_free(chan_list);
+		return -EINVAL;
+	}
+
+	/* make sure num channels is not more than chan list array */
+	if (num_chan > OEM_CAP_MAX_NUM_CHANNELS) {
+		hdd_err("Num of channels-%d > length-%d of chan_list",
+			num_chan, OEM_CAP_MAX_NUM_CHANNELS);
+		cdf_mem_free(chan_list);
+		return -ENOMEM;
+	}
+
+	data_cap->num_channels = num_chan;
+	cdf_mem_copy(data_cap->channel_list, chan_list,
+		     sizeof(uint8_t) * num_chan);
+
+	cdf_mem_free(chan_list);
+	return 0;
+}
+
+/**
  * iw_get_oem_data_cap() - Get OEM Data Capabilities
  * @dev: net device upon which the request was received
  * @info: ioctl request information
@@ -63,15 +135,11 @@
 			struct iw_request_info *info,
 			union iwreq_data *wrqu, char *extra)
 {
-	CDF_STATUS status;
-	t_iw_oem_data_cap oemDataCap;
+	int status;
+	t_iw_oem_data_cap oemDataCap = { {0} };
 	t_iw_oem_data_cap *pHddOemDataCap;
 	hdd_adapter_t *pAdapter = (netdev_priv(dev));
 	hdd_context_t *pHddContext;
-	struct hdd_config *pConfig;
-	uint32_t numChannels;
-	uint8_t chanList[OEM_CAP_MAX_NUM_CHANNELS];
-	uint32_t i;
 	int ret;
 
 	ENTER();
@@ -81,65 +149,12 @@
 	if (0 != ret)
 		return ret;
 
-	pConfig = pHddContext->config;
-	if (!pConfig) {
-		CDF_TRACE(CDF_MODULE_ID_HDD, CDF_TRACE_LEVEL_ERROR,
-			  "%s:HDD configuration is null", __func__);
-		return -ENOENT;
-	}
+	status = populate_oem_data_cap(pAdapter, &oemDataCap);
+	if (!status)
+		return status;
 
-	do {
-		cdf_mem_zero(&oemDataCap, sizeof(oemDataCap));
-		strlcpy(oemDataCap.oem_target_signature, OEM_TARGET_SIGNATURE,
-			OEM_TARGET_SIGNATURE_LEN);
-		oemDataCap.oem_target_type = pHddContext->target_type;
-		oemDataCap.oem_fw_version = pHddContext->target_fw_version;
-		oemDataCap.driver_version.major = QWLAN_VERSION_MAJOR;
-		oemDataCap.driver_version.minor = QWLAN_VERSION_MINOR;
-		oemDataCap.driver_version.patch = QWLAN_VERSION_PATCH;
-		oemDataCap.driver_version.build = QWLAN_VERSION_BUILD;
-		oemDataCap.allowed_dwell_time_min =
-			pConfig->nNeighborScanMinChanTime;
-		oemDataCap.allowed_dwell_time_max =
-			pConfig->nNeighborScanMaxChanTime;
-		oemDataCap.curr_dwell_time_min =
-			sme_get_neighbor_scan_min_chan_time(pHddContext->hHal,
-							    pAdapter->sessionId);
-		oemDataCap.curr_dwell_time_max =
-			sme_get_neighbor_scan_max_chan_time(pHddContext->hHal,
-							    pAdapter->sessionId);
-		oemDataCap.supported_bands = pConfig->nBandCapability;
-
-		/* request for max num of channels */
-		numChannels = WNI_CFG_VALID_CHANNEL_LIST_LEN;
-		status = sme_get_cfg_valid_channels(pHddContext->hHal,
-						    &chanList[0], &numChannels);
-		if (CDF_STATUS_SUCCESS != status) {
-			CDF_TRACE(CDF_MODULE_ID_HDD, CDF_TRACE_LEVEL_ERROR,
-				  "%s:failed to get valid channel list",
-				  __func__);
-			return -ENOENT;
-		} else {
-			/* make sure num channels is not more than chan list array */
-			if (numChannels > OEM_CAP_MAX_NUM_CHANNELS) {
-				CDF_TRACE(CDF_MODULE_ID_HDD,
-					  CDF_TRACE_LEVEL_ERROR,
-					  "%s:Num of channels(%d) more than length(%d) of chanlist",
-					  __func__, numChannels,
-					  OEM_CAP_MAX_NUM_CHANNELS);
-				return -ENOMEM;
-			}
-
-			oemDataCap.num_channels = numChannels;
-			for (i = 0; i < numChannels; i++) {
-				oemDataCap.channel_list[i] = chanList[i];
-			}
-		}
-
-		pHddOemDataCap = (t_iw_oem_data_cap *) (extra);
-		cdf_mem_copy(pHddOemDataCap, &oemDataCap,
-			     sizeof(*pHddOemDataCap));
-	} while (0);
+	pHddOemDataCap = (t_iw_oem_data_cap *) (extra);
+	*pHddOemDataCap = oemDataCap;
 
 	EXIT();
 	return 0;
@@ -511,6 +526,132 @@
 }
 
 /**
+ * oem_process_set_cap_req_msg() - process oem set capability request
+ * @oem_cap_len: Length of OEM capability
+ * @oem_cap: Pointer to OEM capability buffer
+ * @app_pid: process ID, to which rsp message is to be sent
+ *
+ * This function sends oem message to SME
+ *
+ * Return: error code
+ */
+static int oem_process_set_cap_req_msg(int oem_cap_len,
+				       char *oem_cap, int32_t app_pid)
+{
+	CDF_STATUS status;
+	int error_code;
+	struct sk_buff *skb;
+	struct nlmsghdr *nlh;
+	tAniMsgHdr *ani_hdr;
+	uint8_t *buf;
+
+	if (!oem_cap) {
+		hdd_err("oem_cap is null");
+		return -EINVAL;
+	}
+
+	status = sme_oem_update_capability(p_hdd_ctx->hHal,
+					(struct sme_oem_capability *)oem_cap);
+	if (!CDF_IS_STATUS_SUCCESS(status))
+		hdd_err("error updating rm capability, status: %d", status);
+	error_code = cdf_status_to_os_return(status);
+
+	skb = alloc_skb(NLMSG_SPACE(WLAN_NL_MAX_PAYLOAD), GFP_KERNEL);
+	if (skb == NULL) {
+		hdd_err("alloc_skb failed");
+		return -ENOMEM;
+	}
+
+	nlh = (struct nlmsghdr *)skb->data;
+	nlh->nlmsg_pid = 0;     /* from kernel */
+	nlh->nlmsg_flags = 0;
+	nlh->nlmsg_seq = 0;
+	nlh->nlmsg_type = WLAN_NL_MSG_OEM;
+	ani_hdr = NLMSG_DATA(nlh);
+	ani_hdr->type = ANI_MSG_SET_OEM_CAP_RSP;
+	/* 64 bit alignment */
+	ani_hdr->length = sizeof(error_code);
+	nlh->nlmsg_len = NLMSG_LENGTH(sizeof(tAniMsgHdr) + ani_hdr->length);
+
+	/* message body will contain only status code */
+	buf = (char *)((char *)ani_hdr + sizeof(tAniMsgHdr));
+	cdf_mem_copy(buf, &error_code, ani_hdr->length);
+
+	skb_put(skb, NLMSG_SPACE(sizeof(tAniMsgHdr) + ani_hdr->length));
+
+	hdd_info("sending oem response to process pid %d", app_pid);
+
+	(void)nl_srv_ucast(skb, app_pid, MSG_DONTWAIT);
+
+	return error_code;
+}
+
+/**
+ * oem_process_get_cap_req_msg() - process oem get capability request
+ *
+ * This function process the get capability request from OEM and responds
+ * with the capability.
+ *
+ * Return: error code
+ */
+static int oem_process_get_cap_req_msg(void)
+{
+	int error_code;
+	struct oem_get_capability_rsp *cap_rsp;
+	t_iw_oem_data_cap data_cap = { {0} };
+	struct sme_oem_capability oem_cap;
+	hdd_adapter_t *adapter;
+	struct sk_buff *skb;
+	struct nlmsghdr *nlh;
+	tAniMsgHdr *ani_hdr;
+	uint8_t *buf;
+
+	/* for now, STA interface only */
+	adapter = hdd_get_adapter(p_hdd_ctx, WLAN_HDD_INFRA_STATION);
+	if (!adapter) {
+		hdd_err("No adapter for STA mode");
+		return -EINVAL;
+	}
+
+	error_code = populate_oem_data_cap(adapter, &data_cap);
+	if (0 != error_code)
+		return error_code;
+
+	skb = alloc_skb(NLMSG_SPACE(sizeof(tAniMsgHdr) + sizeof(*cap_rsp)),
+			GFP_KERNEL);
+	if (skb == NULL) {
+		hdd_err("alloc_skb failed");
+		return -ENOMEM;
+	}
+
+	nlh = (struct nlmsghdr *)skb->data;
+	nlh->nlmsg_pid = 0;     /* from kernel */
+	nlh->nlmsg_flags = 0;
+	nlh->nlmsg_seq = 0;
+	nlh->nlmsg_type = WLAN_NL_MSG_OEM;
+	ani_hdr = NLMSG_DATA(nlh);
+	ani_hdr->type = ANI_MSG_GET_OEM_CAP_RSP;
+
+	ani_hdr->length = sizeof(*cap_rsp);
+	nlh->nlmsg_len = NLMSG_LENGTH((sizeof(tAniMsgHdr) + ani_hdr->length));
+
+	buf = (char *)((char *)ani_hdr + sizeof(tAniMsgHdr));
+	cdf_mem_copy(buf, &data_cap, sizeof(data_cap));
+
+	buf = (char *) buf +  sizeof(data_cap);
+	cdf_mem_zero(&oem_cap, sizeof(oem_cap));
+	sme_oem_get_capability(p_hdd_ctx->hHal, &oem_cap);
+	cdf_mem_copy(buf, &oem_cap, sizeof(oem_cap));
+
+	skb_put(skb, NLMSG_SPACE((sizeof(tAniMsgHdr) + ani_hdr->length)));
+	hdd_info("send rsp to oem-pid:%d for get_capability",
+		p_hdd_ctx->oem_pid);
+
+	(void)nl_srv_ucast(skb, p_hdd_ctx->oem_pid, MSG_DONTWAIT);
+	return 0;
+}
+
+/**
  * hdd_send_peer_status_ind_to_oem_app() -
  *	Function to send peer status to a registered application
  * @peerMac: MAC address of peer
@@ -768,6 +909,54 @@
 							  sizeof(tAniMsgHdr)));
 		break;
 
+	case ANI_MSG_SET_OEM_CAP_REQ:
+		hdd_info("Received set oem capability req of length:%d from pid: %d",
+			 msg_hdr->length, nlh->nlmsg_pid);
+
+		if ((!p_hdd_ctx->oem_app_registered) ||
+		    (nlh->nlmsg_pid != p_hdd_ctx->oem_pid)) {
+			/* oem app is not registered yet or pid is different */
+			hdd_err("set_oem_capability : app not registered(%d) or incorrect pid(%d)",
+				p_hdd_ctx->oem_app_registered,
+				nlh->nlmsg_pid);
+			send_oem_err_rsp_nlink_msg(nlh->nlmsg_pid,
+						   OEM_ERR_APP_NOT_REGISTERED);
+			return -EPERM;
+		}
+
+		if ((!msg_hdr->length) ||
+			(sizeof(struct sme_oem_capability) < msg_hdr->length)) {
+			hdd_err("Invalid length (%d) in set_oem_capability",
+				msg_hdr->length);
+			send_oem_err_rsp_nlink_msg(nlh->nlmsg_pid,
+						OEM_ERR_INVALID_MESSAGE_LENGTH);
+			return -EPERM;
+		}
+
+		oem_process_set_cap_req_msg(msg_hdr->length,
+					(char *)((char *)msg_hdr +
+						sizeof(tAniMsgHdr)),
+					nlh->nlmsg_pid);
+		break;
+
+	case ANI_MSG_GET_OEM_CAP_REQ:
+		hdd_info("Rcvd get oem capability req of length:%d from pid: %d",
+			 msg_hdr->length, nlh->nlmsg_pid);
+
+		if ((!p_hdd_ctx->oem_app_registered) ||
+		    (nlh->nlmsg_pid != p_hdd_ctx->oem_pid)) {
+			/* oem app is not registered yet or pid is different */
+			hdd_err("get_oem_capability : app not registered(%d) or incorrect pid(%d)",
+				p_hdd_ctx->oem_app_registered,
+				nlh->nlmsg_pid);
+			send_oem_err_rsp_nlink_msg(nlh->nlmsg_pid,
+						   OEM_ERR_APP_NOT_REGISTERED);
+			return -EPERM;
+		}
+
+		oem_process_get_cap_req_msg();
+		break;
+
 	default:
 		CDF_TRACE(CDF_MODULE_ID_HDD, CDF_TRACE_LEVEL_ERROR,
 			  "%s: Received Invalid message type (%d), length (%d)",