qcacld-3.0: Add HDD FIPS infrastructure

As part of the support for the FIPS certification feature add the
HDD infrastructure.

Change-Id: I76a545c42b10a662db04b5994de100c713a46c59
CRs-Fixed: 2065002
diff --git a/Kbuild b/Kbuild
index 6bf4ffa..07bc362 100644
--- a/Kbuild
+++ b/Kbuild
@@ -431,6 +431,10 @@
 HDD_OBJS+=	$(HDD_SRC_DIR)/wlan_hdd_ocb.o
 endif
 
+ifeq ($(CONFIG_WLAN_FEATURE_FIPS), y)
+HDD_OBJS+=	$(HDD_SRC_DIR)/wlan_hdd_fips.o
+endif
+
 ifeq ($(CONFIG_WLAN_FEATURE_LPSS),y)
 HDD_OBJS +=	$(HDD_SRC_DIR)/wlan_hdd_lpass.o
 endif
diff --git a/core/hdd/src/wlan_hdd_fips.c b/core/hdd/src/wlan_hdd_fips.c
new file mode 100644
index 0000000..a472dba
--- /dev/null
+++ b/core/hdd/src/wlan_hdd_fips.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2017 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * DOC: wlan_hdd_fips.c
+ *
+ * WLAN Host Device Driver FIPS Certification Feature
+ */
+
+#include "wlan_hdd_main.h"
+#include "wlan_hdd_fips.h"
+#include "wlan_hdd_request_manager.h"
+#include "qdf_mem.h"
+#include "sme_api.h"
+
+
+#define WLAN_WAIT_TIME_FIPS 5000
+
+
+/**
+ * hdd_fips_context - hdd fips context
+ * @status: status of response. 0: no error, -ENOMEM: unable to allocate
+ *   memory for the response payload
+ * @request: fips request
+ * @response: fips response
+ */
+struct hdd_fips_context {
+	int status;
+	struct fips_params request;
+	struct wmi_host_fips_event_param response;
+};
+
+/**
+ * hdd_fips_event_dup () - duplicate a fips event
+ * @dest: destination event
+ * @src: source event
+ *
+ * Make a "deep" duplicate of a FIPS event
+ *
+ * Return: 0 if the event was duplicated, otherwise an error
+ */
+static int hdd_fips_event_dup(struct wmi_host_fips_event_param *dest,
+			      const struct wmi_host_fips_event_param *src)
+{
+	*dest = *src;
+	if  (dest->data_len) {
+		dest->data = qdf_mem_malloc(dest->data_len);
+		if (!dest->data) {
+			hdd_err("memory allocation failed");
+			return -ENOMEM;
+		}
+		qdf_mem_copy(dest->data, src->data, src->data_len);
+	} else {
+		/* make sure we don't have a rogue pointer */
+		dest->data = NULL;
+	}
+
+	return 0;
+}
+
+/**
+ * hdd_fips_cb () - fips response message handler
+ * @cookie: hdd request cookie
+ * @response: fips response parameters
+ *
+ * Return: none
+ */
+static void hdd_fips_cb(void *cookie,
+			struct wmi_host_fips_event_param *response)
+{
+	struct hdd_request *request;
+	struct hdd_fips_context *context;
+
+	ENTER();
+
+	if (!response) {
+		hdd_err("response is NULL");
+		return;
+	}
+
+	request = hdd_request_get(cookie);
+	if (!request) {
+		hdd_debug("Obsolete request");
+		return;
+	}
+
+	hdd_debug("pdev_id %u, status %u, data_len %u",
+		  response->pdev_id,
+		  response->error_status,
+		  response->data_len);
+	qdf_trace_hex_dump(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_DEBUG,
+			   response->data, response->data_len);
+
+	context = hdd_request_priv(request);
+	if (response->error_status) {
+		context->status = -ETIMEDOUT;
+	} else {
+		context->status = hdd_fips_event_dup(&context->response,
+						     response);
+	}
+
+	hdd_request_complete(request);
+	hdd_request_put(request);
+	EXIT();
+}
+
+static void hdd_fips_context_dealloc(void *priv)
+{
+	struct hdd_fips_context *context = priv;
+
+	qdf_mem_free(context->response.data);
+}
+
+
+static int hdd_fips_validate_request(struct iw_fips_test_request *user_request,
+				     uint32_t request_len)
+{
+	uint32_t expected_data_len;
+
+	if (request_len < sizeof(*user_request)) {
+		hdd_debug("Request len %u is too small", request_len);
+		return -EINVAL;
+	}
+
+	if ((user_request->key_len != FIPS_KEY_LENGTH_128) &&
+	    (user_request->key_len != FIPS_KEY_LENGTH_256)) {
+		hdd_debug("Invalid key len %u", user_request->key_len);
+		return -EINVAL;
+	}
+
+	expected_data_len = request_len - sizeof(*user_request);
+	if (expected_data_len != user_request->data_len) {
+		hdd_debug("Unexpected data_len %u for request_len %u",
+			  user_request->data_len, request_len);
+		return -EINVAL;
+	}
+
+	if ((user_request->mode != FIPS_ENGINE_AES_CTR) &&
+	    (user_request->mode != FIPS_ENGINE_AES_MIC)) {
+		hdd_debug("Invalid mode %u", user_request->mode);
+		return -EINVAL;
+	}
+
+	if ((user_request->operation != FIPS_ENCRYPT_CMD) &&
+	    (user_request->operation != FIPS_DECRYPT_CMD)) {
+		hdd_debug("Invalid operation %u", user_request->operation);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int __hdd_fips_test(struct net_device *dev,
+			  struct iw_request_info *info,
+			  union iwreq_data *wrqu, char *extra)
+{
+	hdd_adapter_t *adapter;
+	hdd_context_t *hdd_ctx;
+	struct iw_fips_test_request *user_request;
+	struct iw_fips_test_response *user_response;
+	uint32_t request_len;
+	int ret;
+	QDF_STATUS qdf_status;
+	void *cookie;
+	struct hdd_request *request;
+	struct hdd_fips_context *context;
+	struct fips_params *fips_request;
+	struct wmi_host_fips_event_param *fips_response;
+	static const struct hdd_request_params params = {
+		.priv_size = sizeof(*context),
+		.timeout_ms = WLAN_WAIT_TIME_FIPS,
+		.dealloc = hdd_fips_context_dealloc,
+	};
+
+	ENTER_DEV(dev);
+
+	adapter = WLAN_HDD_GET_PRIV_PTR(dev);
+	hdd_ctx = WLAN_HDD_GET_CTX(adapter);
+	ret = wlan_hdd_validate_context(hdd_ctx);
+	if (ret)
+		return ret;
+
+	user_request = (struct iw_fips_test_request *)extra;
+	request_len = wrqu->data.length;
+	ret = hdd_fips_validate_request(user_request, request_len);
+	if (ret)
+		return ret;
+
+	request = hdd_request_alloc(&params);
+	if (!request) {
+		hdd_err("Request allocation failure");
+		return -ENOMEM;
+	}
+	context = hdd_request_priv(request);
+	fips_request = &context->request;
+	fips_request->key = &user_request->key[0];
+	fips_request->key_len = user_request->key_len;
+	fips_request->data = &user_request->data[0];
+	fips_request->data_len = user_request->data_len;
+	fips_request->mode = user_request->mode;
+	fips_request->op = user_request->operation;
+	fips_request->pdev_id = WMI_PDEV_ID_1ST;
+
+	cookie = hdd_request_cookie(request);
+	qdf_status = sme_fips_request(hdd_ctx->hHal, &context->request,
+				      hdd_fips_cb, cookie);
+
+	if (!QDF_IS_STATUS_SUCCESS(qdf_status)) {
+		hdd_err("Unable to post fips message");
+		ret = -EINVAL;
+		goto cleanup;
+	}
+
+	ret = hdd_request_wait_for_response(request);
+	if (ret) {
+		hdd_err("Target response timed out");
+		goto cleanup;
+	}
+
+	ret = context->status;
+	if (ret) {
+		hdd_err("Target response processing failed");
+		goto cleanup;
+	}
+
+	fips_response = &context->response;
+	if (fips_response->data_len != fips_request->data_len) {
+		hdd_err("Data length mismatch, got %u, expected %u",
+			fips_response->data_len, fips_request->data_len);
+		ret = -EINVAL;
+		goto cleanup;
+	}
+	user_response = (struct iw_fips_test_response *)extra;
+	user_response->status = context->status;
+	if (user_response->status) {
+		user_response->data_len = 0;
+	} else {
+		user_response->data_len = fips_response->data_len;
+		qdf_mem_copy(user_response->data, fips_response->data,
+			     fips_response->data_len);
+	}
+
+	/*
+	 * By default wireless extensions private ioctls have either
+	 * SET semantics (even numbered ioctls) or GET semantics (odd
+	 * numbered ioctls). This is an even numbered ioctl so the SET
+	 * semantics apply. This means the core kernel ioctl code took
+	 * care of copying the request parameters from userspace to
+	 * kernel space. However this ioctl also needs to return the
+	 * response. Since the core kernel ioctl code doesn't support
+	 * SET ioctls returning anything other than status, we have to
+	 * explicitly copy the result to userspace.
+	 */
+	wrqu->data.length = sizeof(*user_response) + user_response->data_len;
+	if (copy_to_user(wrqu->data.pointer, user_response, wrqu->data.length))
+		ret = -EFAULT;
+
+cleanup:
+	hdd_request_put(request);
+
+	EXIT();
+	return ret;
+}
+
+int hdd_fips_test(struct net_device *dev,
+		  struct iw_request_info *info,
+		  union iwreq_data *wrqu, char *extra)
+{
+	int ret;
+
+	cds_ssr_protect(__func__);
+	ret = __hdd_fips_test(dev, info, wrqu, extra);
+	cds_ssr_unprotect(__func__);
+
+	return ret;
+}
diff --git a/core/hdd/src/wlan_hdd_fips.h b/core/hdd/src/wlan_hdd_fips.h
new file mode 100644
index 0000000..03e7c98
--- /dev/null
+++ b/core/hdd/src/wlan_hdd_fips.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2017 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * DOC: wlan_hdd_fips.h
+ *
+ * WLAN Host Device Driver FIPS Certification Feature
+ */
+
+#ifndef __WLAN_HDD_FIPS_H__
+#define __WLAN_HDD_FIPS_H__
+
+struct net_device;
+struct iw_request_info;
+union iwreq_data;
+struct hdd_adapter_s;
+void fips_test(struct hdd_adapter_s *adapter);
+
+#define FIPS_KEY_LEN 32
+struct iw_fips_test_request {
+	uint32_t operation;
+	uint32_t mode;
+	uint32_t key_len;
+	uint8_t  key[FIPS_KEY_LEN];
+	uint32_t data_len;
+	uint8_t  data[0];
+};
+
+struct iw_fips_test_response {
+	uint32_t status;
+	uint32_t data_len;
+	uint8_t  data[0];
+};
+
+
+/**
+ * hdd_fips_test() - Perform FIPS test
+ * @dev: netdev upon which the FIPS test ioctl was received
+ * @info: ioctl request information
+ * @wrqu: ioctl request data structure
+ * @extra: extra payload for wireless extensions ioctl
+ *
+ * This API implements the FIPS test interface. Upon entry the @extra
+ * buffer will contain a FIPS test vector formated as a &struct
+ * iw_fips_test_request. This vector will be sent to firmware where it
+ * will be run through the appropriate hardware. The result of the
+ * operation will be sent back to userspace via @extra encoded as a
+ * &struct iw_fips_test_response.
+ *
+ * Return: 0 if the test vector was processed, otherwise a negative
+ *         errno.
+ */
+
+int hdd_fips_test(struct net_device *dev,
+		  struct iw_request_info *info,
+		  union iwreq_data *wrqu, char *extra);
+
+#endif /* __WLAN_HDD_FIPS_H__ */
diff --git a/core/hdd/src/wlan_hdd_wext.c b/core/hdd/src/wlan_hdd_wext.c
index dd06cf3..ec8b0ed 100644
--- a/core/hdd/src/wlan_hdd_wext.c
+++ b/core/hdd/src/wlan_hdd_wext.c
@@ -82,6 +82,7 @@
 #include "sme_power_save_api.h"
 #include "wlan_policy_mgr_api.h"
 #include "wlan_hdd_conc_ut.h"
+#include "wlan_hdd_fips.h"
 #include "wlan_hdd_tsf.h"
 #include "wlan_hdd_ocb.h"
 #include "wlan_hdd_napi.h"
@@ -2534,6 +2535,28 @@
 #define MAX_VAR_ARGS         9
 #endif
 
+/*
+ * <ioctl>
+ * fips_test - Perform a FIPS test
+ *
+ * @INPUT: Binary representation of the following packed structure
+ *
+ * @OUTPUT: Binary representation of the following packed structure
+ *
+ * This IOCTL is used to perform FIPS certification testing
+ *
+ * @E.g: iwpriv wlan0 fips_test <test vector>
+ *
+ * iwpriv wlan0 fips_test <tbd>
+ *
+ * Supported Feature: FIPS
+ *
+ * Usage: Internal
+ *
+ * </ioctl>
+ */
+#define WLAN_PRIV_FIPS_TEST (SIOCIWFIRSTPRIV +  8)
+
 /* Private ioctls (with no sub-ioctls) */
 /* note that they must be odd so that they have "get" semantics */
 /*
@@ -2627,7 +2650,6 @@
  */
 #define WLAN_PRIV_GET_TSPEC (SIOCIWFIRSTPRIV + 13)
 
-/* (SIOCIWFIRSTPRIV + 8)  is currently unused */
 /* (SIOCIWFIRSTPRIV + 10) is currently unused */
 /* (SIOCIWFIRSTPRIV + 12) is currently unused */
 /* (SIOCIWFIRSTPRIV + 14) is currently unused */
@@ -12818,6 +12840,7 @@
 		iw_hdd_set_var_ints_getnone,
 	[WLAN_PRIV_SET_NONE_GET_THREE_INT - SIOCIWFIRSTPRIV] =
 							iw_setnone_get_threeint,
+	[WLAN_PRIV_FIPS_TEST - SIOCIWFIRSTPRIV] = hdd_fips_test,
 	[WLAN_PRIV_ADD_TSPEC - SIOCIWFIRSTPRIV] = iw_add_tspec,
 	[WLAN_PRIV_DEL_TSPEC - SIOCIWFIRSTPRIV] = iw_del_tspec,
 	[WLAN_PRIV_GET_TSPEC - SIOCIWFIRSTPRIV] = iw_get_tspec,
@@ -13842,6 +13865,12 @@
 	 "gpio_control"},
 #endif
 	/* handlers for main ioctl */
+	{WLAN_PRIV_FIPS_TEST,
+	 IW_PRIV_TYPE_BYTE | WE_MAX_STR_LEN,
+	 IW_PRIV_TYPE_BYTE | WE_MAX_STR_LEN,
+	 "fips_test"},
+
+	/* handlers for main ioctl */
 	{WLAN_PRIV_ADD_TSPEC,
 	 IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | HDD_WLAN_WMM_PARAM_COUNT,
 	 IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1,