wlan: Introduce HDD Request Manager infrastructure

qcacld-3.0 to prima propagation.

Many operations within the wlan driver occur in an asynchronous
manner. Requests are received by HDD via one of the kernel interfaces
(ioctl, nl80211, virtual file system, etc.). The requests are
translated to an internal format and are then passed to lower layers
for processing. For requests which require a response, that response
comes up from the lower layers in a separate thread of execution,
ultimately resulting in a call to a callback function that was
provided by HDD as part of the initial request. So a mechanism is
needed to synchronize the request and response.

Currently there are various mechanisms which perform these
synchronizations, but experience with them has revealed some flaws. So
a universal mechanism is needed to synchronize the request and
response which addresses all of the known flaws. This framework
provides that mechanism.

Issue: SEC-1813
Change-Id: If4d5912710f8a3b5e87adf76f828a646b7cc2983
CRs-Fixed: 2205457
(cherry picked from commit b22e99861f394c1ca366d62a3ff923c4c71f91a6)
diff --git a/drivers/staging/prima/CORE/HDD/inc/wlan_hdd_request_manager.h b/drivers/staging/prima/CORE/HDD/inc/wlan_hdd_request_manager.h
new file mode 100644
index 0000000..357ef2a
--- /dev/null
+++ b/drivers/staging/prima/CORE/HDD/inc/wlan_hdd_request_manager.h
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2017-2018 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.
+ */
+
+#ifndef __WLAN_HDD_REQUEST_MANAGER_H__
+#define __WLAN_HDD_REQUEST_MANAGER_H__
+
+/**
+ * DOC: WLAN HDD REQUEST MANAGER
+ *
+ * Many operations within the wlan driver occur in an asynchronous
+ * manner. Requests are received by HDD via one of the kernel
+ * interfaces (ioctl, nl80211, virtual file system, etc.). The
+ * requests are translated to an internal format and are then passed
+ * to lower layers, usually via SME, for processing. For requests
+ * which require a response, that response comes up from the lower
+ * layers in a separate thread of execution, ultimately resulting in a
+ * call to a callback function that was provided by HDD as part of the
+ * initial request. So a mechanism is needed to synchronize the
+ * request and response. This framework provides that mechanism.
+ *
+ * Once the framework has been initialized, the typical sequence of
+ * events is as follows:
+ *
+ * Request Thread:
+ * 1. Create a &struct hdd_request_params which describes the request.
+ * 2. Call hdd_request_alloc() to allocate a &struct hdd_request.
+ * 3. Call hdd_request_priv() to get a pointer to the private data.
+ * 4. Place any information which must be shared with the Response
+ *    Callback in the private data area.
+ * 5. Call hdd_request_cookie() to get the unique cookie assigned
+ *    to the request.
+ * 6. Call the underlying request handling API, passing the cookie
+ *    as the callback's private context.
+ * 7. Call hdd_request_wait_for_response() to wait for the response
+ *    (or for the request to time out).
+ * 8. Use the return status to see if the request was successful. If
+ *    it was, retrieve any response information from the private
+ *    structure and prepare a response for userspace.
+ * 9. Call hdd_request_put() to relinquish access to the request.
+ * 10. Return status to the caller.
+ *
+ * Response Callback:
+ * 1. Call hdd_request_get() with the provided cookie to see if the
+ *    request structure is still valid.  If it returns %NULL then
+ *    return since this means the request thread has already timed
+ *    out.
+ * 2. Call hdd_request_priv() to get access to the private data area.
+ * 3. Write response data into the private data area.
+ * 4. Call hdd_request_complete() to indicate that the response is
+ *    ready to be processed by the request thread.
+ * 5. Call hdd_request_put() to relinquish the callback function's
+ *    reference to the request.
+ */
+
+/* this is opaque to clients */
+struct hdd_request;
+
+/**
+ * typedef hdd_request_dealloc - Private data deallocation function
+ */
+typedef void (*hdd_request_dealloc)(void *priv);
+
+/**
+ * struct hdd_request_params - HDD request parameters
+ * @priv_size: Size of the private data area required to pass
+ *      information between the request thread and the response callback.
+ * @timeout_ms: The amount of time to wait for a response in milliseconds.
+ * @dealloc: Function to be called when the request is destroyed to
+ *      deallocate any allocations made in the private area of the
+ *      request struct. Can be %NULL if no private allocations are
+ *      made.
+ */
+struct hdd_request_params {
+	uint32_t priv_size;
+	uint32_t timeout_ms;
+	hdd_request_dealloc dealloc;
+};
+
+/**
+ * hdd_request_alloc() - Allocate a request struct
+ * @params: parameter block that specifies the attributes of the
+ *      request
+ *
+ * This function will attempt to allocate a &struct hdd_request with
+ * the specified @params. If successful, the caller can then use
+ * request struct to make an asynchronous request. Once the request is
+ * no longer needed, the reference should be relinquished via a call
+ * to hdd_request_put().
+ *
+ * Return: A pointer to an allocated &struct hdd_request (which also
+ * contains room for the private buffer) if the allocation is
+ * successful, %NULL if the allocation fails.
+ */
+struct hdd_request *hdd_request_alloc(const struct hdd_request_params *params);
+
+/**
+ * hdd_request_priv() - Get pointer to request private data
+ * @request: The request struct that contains the private data
+ *
+ * This function will return a pointer to the private data area that
+ * is part of the request struct. The caller must already have a valid
+ * reference to @request from either hdd_request_alloc() or
+ * hdd_request_get().
+ *
+ * Returns: pointer to the private data area. Note that this pointer
+ * will always be an offset from the input @request pointer and hence
+ * this function will never return %NULL.
+ */
+void *hdd_request_priv(struct hdd_request *request);
+
+/**
+ * hdd_request_cookie() - Get cookie of a request
+ * @request: The request struct associated with the request
+ *
+ * This function will return the unique cookie that has been assigned
+ * to the request. This cookie can subsequently be passed to
+ * hdd_request_get() to retrieve the request.
+ *
+ * Note that the cookie is defined as a void pointer as it is intended
+ * to be passed as an opaque context pointer from HDD to underlying
+ * layers when making a request, and subsequently passed back to HDD
+ * as an opaque pointer in an asynchronous callback.
+ *
+ * Returns: The cookie assigned to the request.
+ */
+void *hdd_request_cookie(struct hdd_request *request);
+
+/**
+ * hdd_request_get() - Get a reference to a request struct
+ * @cookie: The cookie of the request struct that needs to be
+ *      referenced
+ *
+ * This function will use the cookie to determine if the associated
+ * request struct is valid, and if so, will increment the reference
+ * count of the struct. This means the caller is guaranteed that the
+ * request struct is valid and the underlying private data can be
+ * dereferenced.
+ *
+ * Returns: The pointer to the request struct associated with @cookie
+ * if the request is still valid, %NULL if the underlying request
+ * struct is no longer valid.
+ */
+struct hdd_request *hdd_request_get(void *cookie);
+
+/**
+ * hdd_request_put() - Release a reference to a request struct
+ * @request: The request struct that no longer needs to be referenced
+ *
+ * This function will decrement the reference count of the struct, and
+ * will clean up the request if this is the last reference. The caller
+ * must already have a valid reference to @request, either from
+ * hdd_request_alloc() or hdd_request_get().
+ *
+ * Returns: Nothing
+ */
+void hdd_request_put(struct hdd_request *request);
+
+/**
+ * hdd_request_wait_for_response() - Wait for a response
+ * @request: The request struct associated with the request
+ *
+ * This function will wait until either a response is received and
+ * communicated via hdd_request_complete(), or until the request
+ * timeout period expires.
+ *
+ * Returns: 0 if a response was received, -ETIMEDOUT if the response
+ * timed out.
+ */
+int hdd_request_wait_for_response(struct hdd_request *request);
+
+/**
+ * hdd_request_complete() - Complete a request
+ * @request: The request struct associated with the request
+ *
+ * This function is used to indicate that a response has been received
+ * and that any information required by the request thread has been
+ * copied into the private data area of the request struct. This will
+ * unblock any hdd_request_wait_for_response() that is pending on this
+ * @request.
+ *
+ * Returns: Nothing
+ */
+void hdd_request_complete(struct hdd_request *request);
+
+/**
+ * hdd_request_manager_init() - Initialize the HDD Request Manager
+ *
+ * This function must be called during system initialization to
+ * initialize the HDD Request Manager.
+ *
+ * Returns: Nothing
+ */
+void hdd_request_manager_init(void);
+
+/**
+ * hdd_request_manager_deinit() - Deinitialize the HDD Request Manager
+ *
+ * This function must be called during system shutdown to deinitialize
+ * the HDD Request Manager.
+ *
+ * Returns: Nothing
+ */
+void hdd_request_manager_deinit(void);
+
+#endif /* __WLAN_HDD_REQUEST_MANAGER_H__ */
diff --git a/drivers/staging/prima/CORE/HDD/src/wlan_hdd_main.c b/drivers/staging/prima/CORE/HDD/src/wlan_hdd_main.c
index 9a96d1e..3eaad2f 100755
--- a/drivers/staging/prima/CORE/HDD/src/wlan_hdd_main.c
+++ b/drivers/staging/prima/CORE/HDD/src/wlan_hdd_main.c
@@ -120,6 +120,7 @@
 #endif
 #include "wlan_hdd_debugfs.h"
 #include "sapInternal.h"
+#include "wlan_hdd_request_manager.h"
 
 #ifdef MODULE
 #define WLAN_MODULE_NAME  module_name(THIS_MODULE)
@@ -8921,6 +8922,8 @@
        pHddCtx->cfg_ini= NULL;
    }
 
+    hdd_request_manager_deinit();
+
    /* FTM mode, WIPHY did not registered
       If un-register here, system crash will happen */
    if (VOS_FTM_MODE != hdd_get_conparam())
@@ -9738,6 +9741,8 @@
       goto err_free_hdd_context;
    }
 
+   hdd_request_manager_init();
+
    vos_mem_zero(pHddCtx->cfg_ini, sizeof( hdd_config_t ));
 
    // Read and parse the qcom_cfg.ini file
@@ -10532,6 +10537,7 @@
       vos_watchdog_close(pVosContext);
 
 err_config:
+   hdd_request_manager_deinit();
    kfree(pHddCtx->cfg_ini);
    pHddCtx->cfg_ini= NULL;
 
diff --git a/drivers/staging/prima/CORE/HDD/src/wlan_hdd_request_manager.c b/drivers/staging/prima/CORE/HDD/src/wlan_hdd_request_manager.c
new file mode 100644
index 0000000..2f96e98
--- /dev/null
+++ b/drivers/staging/prima/CORE/HDD/src/wlan_hdd_request_manager.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2017-2018 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.
+ */
+
+#include <linux/kernel.h>
+#include "wlan_hdd_request_manager.h"
+#include "wlan_hdd_main.h"
+#include "wlan_hdd_dp_utils.h"
+#include "vos_event.h"
+#include "vos_memory.h"
+
+/* arbitrary value */
+#define MAX_NUM_REQUESTS 20
+
+static bool is_initialized;
+static hdd_list_t requests;
+static spinlock_t spinlock;
+static void *cookie;
+
+struct hdd_request {
+	hdd_list_node_t node;
+	void *cookie;
+	uint32_t reference_count;
+	struct hdd_request_params params;
+	vos_event_t completed;
+};
+
+/* must be called with spinlock held */
+static void hdd_request_unlink(struct hdd_request *request)
+{
+	hdd_list_remove_node(&requests, &request->node);
+}
+
+static void hdd_request_destroy(struct hdd_request *request)
+{
+	struct hdd_request_params *params;
+
+	params = &request->params;
+	if (params->dealloc) {
+		void *priv = hdd_request_priv(request);
+
+		params->dealloc(priv);
+	}
+	vos_event_destroy(&request->completed);
+	vos_mem_free(request);
+}
+
+/* must be called with spinlock held */
+static struct hdd_request *hdd_request_find(void *cookie)
+{
+	VOS_STATUS status;
+	struct hdd_request *request;
+	hdd_list_node_t *node;
+
+	status = hdd_list_peek_front(&requests, &node);
+	while (VOS_IS_STATUS_SUCCESS(status)) {
+		request = container_of(node, struct hdd_request, node);
+		if (request->cookie == cookie)
+			return request;
+		status = hdd_list_peek_next(&requests, node, &node);
+	}
+
+	return NULL;
+}
+
+struct hdd_request *hdd_request_alloc(const struct hdd_request_params *params)
+{
+	size_t length;
+	struct hdd_request *request;
+
+	if (!is_initialized) {
+		hddLog(VOS_TRACE_LEVEL_ERROR,
+		       FL("invoked when not initialized from %pS"),
+		       (void *)_RET_IP_);
+		return NULL;
+	}
+
+	length = sizeof(*request) + params->priv_size;
+	request = vos_mem_malloc(length);
+	if (!request) {
+		hddLog(VOS_TRACE_LEVEL_ERROR, FL("allocation failed for %pS"),
+		       (void *)_RET_IP_);
+		return NULL;
+	}
+	request->reference_count = 1;
+	request->params = *params;
+	vos_event_init(&request->completed);
+	spin_lock_bh(&spinlock);
+	request->cookie = cookie++;
+	hdd_list_insert_back(&requests, &request->node);
+	spin_unlock_bh(&spinlock);
+	hddLog(VOS_TRACE_LEVEL_INFO, FL("request %pK, cookie %pK, caller %pS"),
+	       request, request->cookie, (void *)_RET_IP_);
+
+	return request;
+}
+
+void *hdd_request_priv(struct hdd_request *request)
+{
+	/* private data area immediately follows the struct hdd_request */
+	return request + 1;
+}
+
+void *hdd_request_cookie(struct hdd_request *request)
+{
+	return request->cookie;
+}
+
+struct hdd_request *hdd_request_get(void *cookie)
+{
+	struct hdd_request *request;
+
+	if (!is_initialized) {
+		hddLog(VOS_TRACE_LEVEL_ERROR,
+		       FL("invoked when not initialized from %pS"),
+		       (void *)_RET_IP_);
+		return NULL;
+	}
+	spin_lock_bh(&spinlock);
+	request = hdd_request_find(cookie);
+	if (request)
+		request->reference_count++;
+	spin_unlock_bh(&spinlock);
+	hddLog(VOS_TRACE_LEVEL_INFO, FL("cookie %pK, request %pK, caller %pS"),
+	       cookie, request, (void *)_RET_IP_);
+
+	return request;
+}
+
+void hdd_request_put(struct hdd_request *request)
+{
+	bool unlinked = false;
+
+	hddLog(VOS_TRACE_LEVEL_INFO, FL("request %pK, cookie %pK, caller %pS"),
+	       request, request->cookie, (void *)_RET_IP_);
+	spin_lock_bh(&spinlock);
+	request->reference_count--;
+	if (0 == request->reference_count) {
+		hdd_request_unlink(request);
+		unlinked = true;
+	}
+	spin_unlock_bh(&spinlock);
+	if (unlinked)
+		hdd_request_destroy(request);
+}
+
+int hdd_request_wait_for_response(struct hdd_request *request)
+{
+	return vos_wait_single_event(&request->completed,
+				     request->params.timeout_ms);
+}
+
+void hdd_request_complete(struct hdd_request *request)
+{
+	(void)vos_event_set(&request->completed);
+}
+
+void hdd_request_manager_init(void)
+{
+	hddLog(VOS_TRACE_LEVEL_INFO, FL("%pS"), (void *)_RET_IP_);
+	if (is_initialized)
+		return;
+
+	hdd_list_init(&requests, MAX_NUM_REQUESTS);
+	spin_lock_init(&spinlock);
+	is_initialized = true;
+}
+
+/*
+ * hdd_request_manager_deinit implementation note:
+ * It is intentional that we do not destroy the list or the spinlock.
+ * This allows threads to still access the infrastructure even when it
+ * has been deinitialized. Since neither lists nor spinlocks consume
+ * resources this does not result in a resource leak.
+ */
+void hdd_request_manager_deinit(void)
+{
+	hddLog(VOS_TRACE_LEVEL_INFO, FL("%pS"), (void *)_RET_IP_);
+	is_initialized = false;
+}
diff --git a/drivers/staging/prima/Kbuild b/drivers/staging/prima/Kbuild
index cebcbea..46ef90a 100644
--- a/drivers/staging/prima/Kbuild
+++ b/drivers/staging/prima/Kbuild
@@ -134,6 +134,7 @@
 		$(HDD_SRC_DIR)/wlan_hdd_main.o \
 		$(HDD_SRC_DIR)/wlan_hdd_mib.o \
 		$(HDD_SRC_DIR)/wlan_hdd_oemdata.o \
+		$(HDD_SRC_DIR)/wlan_hdd_request_manager.o \
 		$(HDD_SRC_DIR)/wlan_hdd_scan.o \
 		$(HDD_SRC_DIR)/wlan_hdd_softap_tx_rx.o \
 		$(HDD_SRC_DIR)/wlan_hdd_tx_rx.o \