blob: d2c4dadb4ccc6f98b9cdb3d1d1da7fee43ed9c28 [file] [log] [blame]
/*
* Copyright (c) 2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
*
* 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.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
#include <linux/kernel.h>
#include "wlan_hdd_request_manager.h"
#include "wlan_hdd_main.h"
#include "qdf_list.h"
#include "qdf_event.h"
#include "qdf_mem.h"
/* arbitrary value */
#define MAX_NUM_REQUESTS 20
static bool is_initialized;
static qdf_list_t requests;
static qdf_spinlock_t spinlock;
static void *cookie;
struct hdd_request {
qdf_list_node_t node;
void *cookie;
uint32_t reference_count;
struct hdd_request_params params;
qdf_event_t completed;
};
/* must be called with spinlock held */
static void hdd_request_unlink(struct hdd_request *request)
{
qdf_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);
}
qdf_event_destroy(&request->completed);
qdf_mem_free(request);
}
/* must be called with spinlock held */
static struct hdd_request *hdd_request_find(void *cookie)
{
QDF_STATUS status;
struct hdd_request *request;
qdf_list_node_t *node;
status = qdf_list_peek_front(&requests, &node);
while (QDF_IS_STATUS_SUCCESS(status)) {
request = qdf_container_of(node, struct hdd_request, node);
if (request->cookie == cookie)
return request;
status = qdf_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) {
hdd_err("invoked when not initialized from %pS",
(void *)_RET_IP_);
return NULL;
}
length = sizeof(*request) + params->priv_size;
request = qdf_mem_malloc(length);
if (!request) {
hdd_err("allocation failed for %pS", (void *)_RET_IP_);
return NULL;
}
request->reference_count = 1;
request->params = *params;
qdf_event_create(&request->completed);
qdf_spin_lock_bh(&spinlock);
request->cookie = cookie++;
qdf_list_insert_back(&requests, &request->node);
qdf_spin_unlock_bh(&spinlock);
hdd_debug("request %p, cookie %p, 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) {
hdd_err("invoked when not initialized from %pS",
(void *)_RET_IP_);
return NULL;
}
qdf_spin_lock_bh(&spinlock);
request = hdd_request_find(cookie);
if (request)
request->reference_count++;
qdf_spin_unlock_bh(&spinlock);
hdd_debug("cookie %p, request %p, caller %pS",
cookie, request, (void *)_RET_IP_);
return request;
}
void hdd_request_put(struct hdd_request *request)
{
bool unlinked = false;
hdd_debug("request %p, cookie %p, caller %pS",
request, request->cookie, (void *)_RET_IP_);
qdf_spin_lock_bh(&spinlock);
request->reference_count--;
if (0 == request->reference_count) {
hdd_request_unlink(request);
unlinked = true;
}
qdf_spin_unlock_bh(&spinlock);
if (unlinked)
hdd_request_destroy(request);
}
int hdd_request_wait_for_response(struct hdd_request *request)
{
QDF_STATUS status;
status = qdf_wait_single_event(&request->completed,
request->params.timeout_ms);
return qdf_status_to_os_return(status);
}
void hdd_request_complete(struct hdd_request *request)
{
(void) qdf_event_set(&request->completed);
}
void hdd_request_manager_init(void)
{
hdd_debug("%pS", (void *)_RET_IP_);
if (is_initialized)
return;
qdf_list_create(&requests, MAX_NUM_REQUESTS);
qdf_spinlock_create(&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)
{
hdd_debug("%pS", (void *)_RET_IP_);
is_initialized = false;
}