diff --git a/core/hdd/src/wlan_hdd_request_manager.c b/core/hdd/src/wlan_hdd_request_manager.c new file mode 100644 index 0000000000..d2c4dadb4c --- /dev/null +++ b/core/hdd/src/wlan_hdd_request_manager.c @@ -0,0 +1,203 @@ +/* + * 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 +#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; +} diff --git a/core/hdd/src/wlan_hdd_request_manager.h b/core/hdd/src/wlan_hdd_request_manager.h new file mode 100644 index 0000000000..cb2d425b71 --- /dev/null +++ b/core/hdd/src/wlan_hdd_request_manager.h @@ -0,0 +1,229 @@ +/* + * 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. + */ + +#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__ */