blob: 5adec7d5f750bd4133fe3615e144c00a48f284a7 [file] [log] [blame]
/*
* Copyright (c) 2014-2020 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 <dp_txrx.h>
#include "dp_peer.h"
#include "dp_internal.h"
#include <cdp_txrx_cmn_struct.h>
#include <cdp_txrx_peer_ops.h>
#include <cds_sched.h>
/* Timeout in ms to wait for a DP rx thread */
#define DP_RX_THREAD_WAIT_TIMEOUT 200
#define DP_RX_TM_DEBUG 0
#if DP_RX_TM_DEBUG
/**
* dp_rx_tm_walk_skb_list() - Walk skb list and print members
* @nbuf_list - nbuf list to print
*
* Returns: None
*/
static inline void dp_rx_tm_walk_skb_list(qdf_nbuf_t nbuf_list)
{
qdf_nbuf_t nbuf;
int i = 0;
nbuf = nbuf_list;
while (nbuf) {
dp_debug("%d nbuf:%pK nbuf->next:%pK nbuf->data:%pK", i,
nbuf, qdf_nbuf_next(nbuf), qdf_nbuf_data(nbuf));
nbuf = qdf_nbuf_next(nbuf);
i++;
}
}
#else
static inline void dp_rx_tm_walk_skb_list(qdf_nbuf_t nbuf_list)
{ }
#endif /* DP_RX_TM_DEBUG */
/**
* dp_rx_tm_get_soc_handle() - get soc handle from struct dp_rx_tm_handle_cmn
* @rx_tm_handle_cmn - rx thread manager cmn handle
*
* Returns: ol_txrx_soc_handle on success, NULL on failure.
*/
static inline
ol_txrx_soc_handle dp_rx_tm_get_soc_handle(struct dp_rx_tm_handle_cmn *rx_tm_handle_cmn)
{
struct dp_txrx_handle_cmn *txrx_handle_cmn;
ol_txrx_soc_handle soc;
txrx_handle_cmn =
dp_rx_thread_get_txrx_handle(rx_tm_handle_cmn);
soc = dp_txrx_get_soc_from_ext_handle(txrx_handle_cmn);
return soc;
}
/**
* dp_rx_tm_thread_dump_stats() - display stats for a rx_thread
* @rx_thread - rx_thread pointer for which the stats need to be
* displayed
*
* Returns: None
*/
static void dp_rx_tm_thread_dump_stats(struct dp_rx_thread *rx_thread)
{
uint8_t reo_ring_num;
uint32_t off = 0;
char nbuf_queued_string[100];
uint32_t total_queued = 0;
uint32_t temp = 0;
qdf_mem_zero(nbuf_queued_string, sizeof(nbuf_queued_string));
for (reo_ring_num = 0; reo_ring_num < DP_RX_TM_MAX_REO_RINGS;
reo_ring_num++) {
temp = rx_thread->stats.nbuf_queued[reo_ring_num];
if (!temp)
continue;
total_queued += temp;
if (off >= sizeof(nbuf_queued_string))
continue;
off += qdf_scnprintf(&nbuf_queued_string[off],
sizeof(nbuf_queued_string) - off,
"reo[%u]:%u ", reo_ring_num, temp);
}
if (!total_queued)
return;
dp_info("thread:%u - qlen:%u queued:(total:%u %s) dequeued:%u stack:%u gro_flushes: %u gro_flushes_by_vdev_del: %u rx_flushes: %u max_len:%u invalid(peer:%u vdev:%u rx-handle:%u others:%u enq fail:%u)",
rx_thread->id,
qdf_nbuf_queue_head_qlen(&rx_thread->nbuf_queue),
total_queued,
nbuf_queued_string,
rx_thread->stats.nbuf_dequeued,
rx_thread->stats.nbuf_sent_to_stack,
rx_thread->stats.gro_flushes,
rx_thread->stats.gro_flushes_by_vdev_del,
rx_thread->stats.rx_flushed,
rx_thread->stats.nbufq_max_len,
rx_thread->stats.dropped_invalid_peer,
rx_thread->stats.dropped_invalid_vdev,
rx_thread->stats.dropped_invalid_os_rx_handles,
rx_thread->stats.dropped_others,
rx_thread->stats.dropped_enq_fail);
}
QDF_STATUS dp_rx_tm_dump_stats(struct dp_rx_tm_handle *rx_tm_hdl)
{
int i;
for (i = 0; i < rx_tm_hdl->num_dp_rx_threads; i++) {
if (!rx_tm_hdl->rx_thread[i])
continue;
dp_rx_tm_thread_dump_stats(rx_tm_hdl->rx_thread[i]);
}
return QDF_STATUS_SUCCESS;
}
#ifdef FEATURE_ALLOW_PKT_DROPPING
/*
* dp_check_and_update_pending() - Check and Set RX Pending flag
* @tm_handle_cmn - DP thread pointer
*
* Returns: QDF_STATUS_SUCCESS on success or qdf error code on
* failure
*/
static inline
QDF_STATUS dp_check_and_update_pending(struct dp_rx_tm_handle_cmn
*tm_handle_cmn)
{
struct dp_txrx_handle_cmn *txrx_handle_cmn;
struct dp_rx_tm_handle *rx_tm_hdl =
(struct dp_rx_tm_handle *)tm_handle_cmn;
struct dp_soc *dp_soc;
uint32_t rx_pending_hl_threshold;
uint32_t rx_pending_lo_threshold;
uint32_t nbuf_queued_total = 0;
uint32_t nbuf_dequeued_total = 0;
uint32_t pending = 0;
int i;
txrx_handle_cmn =
dp_rx_thread_get_txrx_handle(tm_handle_cmn);
if (!txrx_handle_cmn) {
dp_err("invalid txrx_handle_cmn!");
QDF_BUG(0);
return QDF_STATUS_E_FAILURE;
}
dp_soc = (struct dp_soc *)dp_txrx_get_soc_from_ext_handle(
txrx_handle_cmn);
if (!dp_soc) {
dp_err("invalid soc!");
QDF_BUG(0);
return QDF_STATUS_E_FAILURE;
}
rx_pending_hl_threshold = wlan_cfg_rx_pending_hl_threshold(
dp_soc->wlan_cfg_ctx);
rx_pending_lo_threshold = wlan_cfg_rx_pending_lo_threshold(
dp_soc->wlan_cfg_ctx);
for (i = 0; i < rx_tm_hdl->num_dp_rx_threads; i++) {
if (likely(rx_tm_hdl->rx_thread[i])) {
nbuf_queued_total +=
rx_tm_hdl->rx_thread[i]->stats.nbuf_queued_total;
nbuf_dequeued_total +=
rx_tm_hdl->rx_thread[i]->stats.nbuf_dequeued;
}
}
if (nbuf_queued_total > nbuf_dequeued_total)
pending = nbuf_queued_total - nbuf_dequeued_total;
if (unlikely(pending > rx_pending_hl_threshold))
qdf_atomic_set(&rx_tm_hdl->allow_dropping, 1);
else if (pending < rx_pending_lo_threshold)
qdf_atomic_set(&rx_tm_hdl->allow_dropping, 0);
return QDF_STATUS_SUCCESS;
}
#else
static inline
QDF_STATUS dp_check_and_update_pending(struct dp_rx_tm_handle_cmn
*tm_handle_cmn)
{
return QDF_STATUS_SUCCESS;
}
#endif
/**
* dp_rx_tm_thread_enqueue() - enqueue nbuf list into rx_thread
* @rx_thread - rx_thread in which the nbuf needs to be queued
* @nbuf_list - list of packets to be queued into the thread
*
* Enqueue packet into rx_thread and wake it up. The function
* moves the next pointer of the nbuf_list into the ext list of
* the first nbuf for storage into the thread. Only the first
* nbuf is queued into the thread nbuf queue. The reverse is
* done at the time of dequeue.
*
* Returns: QDF_STATUS_SUCCESS on success or qdf error code on
* failure
*/
static QDF_STATUS dp_rx_tm_thread_enqueue(struct dp_rx_thread *rx_thread,
qdf_nbuf_t nbuf_list)
{
qdf_nbuf_t head_ptr, next_ptr_list;
uint32_t temp_qlen;
uint32_t num_elements_in_nbuf;
uint32_t nbuf_queued;
struct dp_rx_tm_handle_cmn *tm_handle_cmn;
uint8_t reo_ring_num = QDF_NBUF_CB_RX_CTX_ID(nbuf_list);
qdf_wait_queue_head_t *wait_q_ptr;
uint8_t allow_dropping;
tm_handle_cmn = rx_thread->rtm_handle_cmn;
if (!tm_handle_cmn) {
dp_alert("tm_handle_cmn is null!");
QDF_BUG(0);
return QDF_STATUS_E_FAILURE;
}
wait_q_ptr = &rx_thread->wait_q;
if (reo_ring_num >= DP_RX_TM_MAX_REO_RINGS) {
dp_alert("incorrect ring %u", reo_ring_num);
QDF_BUG(0);
return QDF_STATUS_E_FAILURE;
}
num_elements_in_nbuf = QDF_NBUF_CB_RX_NUM_ELEMENTS_IN_LIST(nbuf_list);
nbuf_queued = num_elements_in_nbuf;
allow_dropping = qdf_atomic_read(
&((struct dp_rx_tm_handle *)tm_handle_cmn)->allow_dropping);
if (unlikely(allow_dropping)) {
qdf_nbuf_list_free(nbuf_list);
rx_thread->stats.dropped_enq_fail += num_elements_in_nbuf;
nbuf_queued = 0;
goto enq_done;
}
dp_rx_tm_walk_skb_list(nbuf_list);
head_ptr = nbuf_list;
/* Ensure head doesn't have an ext list */
while (qdf_unlikely(head_ptr && qdf_nbuf_get_ext_list(head_ptr))) {
QDF_NBUF_CB_RX_NUM_ELEMENTS_IN_LIST(head_ptr) = 1;
num_elements_in_nbuf--;
next_ptr_list = head_ptr->next;
qdf_nbuf_set_next(head_ptr, NULL);
qdf_nbuf_queue_head_enqueue_tail(&rx_thread->nbuf_queue,
head_ptr);
head_ptr = next_ptr_list;
}
if (!head_ptr)
goto enq_done;
QDF_NBUF_CB_RX_NUM_ELEMENTS_IN_LIST(head_ptr) = num_elements_in_nbuf;
next_ptr_list = head_ptr->next;
if (next_ptr_list) {
/* move ->next pointer to ext list */
qdf_nbuf_append_ext_list(head_ptr, next_ptr_list, 0);
dp_debug("appended next_ptr_list %pK to nbuf %pK ext list %pK",
qdf_nbuf_next(nbuf_list), nbuf_list,
qdf_nbuf_get_ext_list(nbuf_list));
}
qdf_nbuf_set_next(head_ptr, NULL);
qdf_nbuf_queue_head_enqueue_tail(&rx_thread->nbuf_queue, head_ptr);
enq_done:
temp_qlen = qdf_nbuf_queue_head_qlen(&rx_thread->nbuf_queue);
rx_thread->stats.nbuf_queued[reo_ring_num] += nbuf_queued;
rx_thread->stats.nbuf_queued_total += nbuf_queued;
dp_check_and_update_pending(tm_handle_cmn);
if (temp_qlen > rx_thread->stats.nbufq_max_len)
rx_thread->stats.nbufq_max_len = temp_qlen;
dp_debug("enqueue packet thread %pK wait queue %pK qlen %u",
rx_thread, wait_q_ptr,
qdf_nbuf_queue_head_qlen(&rx_thread->nbuf_queue));
qdf_set_bit(RX_POST_EVENT, &rx_thread->event_flag);
qdf_wake_up_interruptible(wait_q_ptr);
return QDF_STATUS_SUCCESS;
}
static QDF_STATUS dp_rx_tm_thread_gro_flush_ind(struct dp_rx_thread *rx_thread)
{
struct dp_rx_tm_handle_cmn *tm_handle_cmn;
qdf_wait_queue_head_t *wait_q_ptr;
tm_handle_cmn = rx_thread->rtm_handle_cmn;
wait_q_ptr = &rx_thread->wait_q;
qdf_atomic_set(&rx_thread->gro_flush_ind, 1);
dp_debug("Flush indication received");
qdf_set_bit(RX_POST_EVENT, &rx_thread->event_flag);
qdf_wake_up_interruptible(wait_q_ptr);
return QDF_STATUS_SUCCESS;
}
/**
* dp_rx_thread_adjust_nbuf_list() - create an nbuf list from the frag list
* @head - nbuf list to be created
*
* Returns: void
*/
static void dp_rx_thread_adjust_nbuf_list(qdf_nbuf_t head)
{
qdf_nbuf_t next_ptr_list, nbuf_list;
nbuf_list = head;
if (head && QDF_NBUF_CB_RX_NUM_ELEMENTS_IN_LIST(head) > 1) {
/* move ext list to ->next pointer */
next_ptr_list = qdf_nbuf_get_ext_list(head);
qdf_nbuf_append_ext_list(head, NULL, 0);
qdf_nbuf_set_next(nbuf_list, next_ptr_list);
dp_rx_tm_walk_skb_list(nbuf_list);
}
}
/**
* dp_rx_tm_thread_dequeue() - dequeue nbuf list from rx_thread
* @rx_thread - rx_thread from which the nbuf needs to be dequeued
*
* Returns: nbuf or nbuf_list dequeued from rx_thread
*/
static qdf_nbuf_t dp_rx_tm_thread_dequeue(struct dp_rx_thread *rx_thread)
{
qdf_nbuf_t head;
head = qdf_nbuf_queue_head_dequeue(&rx_thread->nbuf_queue);
dp_rx_thread_adjust_nbuf_list(head);
dp_debug("Dequeued %pK nbuf_list", head);
return head;
}
/**
* dp_rx_thread_process_nbufq() - process nbuf queue of a thread
* @rx_thread - rx_thread whose nbuf queue needs to be processed
*
* Returns: 0 on success, error code on failure
*/
static int dp_rx_thread_process_nbufq(struct dp_rx_thread *rx_thread)
{
qdf_nbuf_t nbuf_list;
uint8_t vdev_id;
ol_txrx_rx_fp stack_fn;
ol_osif_vdev_handle osif_vdev;
ol_txrx_soc_handle soc;
uint32_t num_list_elements = 0;
struct dp_txrx_handle_cmn *txrx_handle_cmn;
txrx_handle_cmn =
dp_rx_thread_get_txrx_handle(rx_thread->rtm_handle_cmn);
soc = dp_txrx_get_soc_from_ext_handle(txrx_handle_cmn);
if (!soc) {
dp_err("invalid soc!");
QDF_BUG(0);
return -EFAULT;
}
dp_debug("enter: qlen %u",
qdf_nbuf_queue_head_qlen(&rx_thread->nbuf_queue));
nbuf_list = dp_rx_tm_thread_dequeue(rx_thread);
while (nbuf_list) {
num_list_elements =
QDF_NBUF_CB_RX_NUM_ELEMENTS_IN_LIST(nbuf_list);
rx_thread->stats.nbuf_dequeued += num_list_elements;
vdev_id = QDF_NBUF_CB_RX_VDEV_ID(nbuf_list);
cdp_get_os_rx_handles_from_vdev(soc, vdev_id, &stack_fn,
&osif_vdev);
dp_debug("rx_thread %pK sending packet %pK to stack",
rx_thread, nbuf_list);
if (!stack_fn || !osif_vdev ||
QDF_STATUS_SUCCESS != stack_fn(osif_vdev, nbuf_list)) {
rx_thread->stats.dropped_invalid_os_rx_handles +=
num_list_elements;
qdf_nbuf_list_free(nbuf_list);
} else {
rx_thread->stats.nbuf_sent_to_stack +=
num_list_elements;
}
nbuf_list = dp_rx_tm_thread_dequeue(rx_thread);
}
dp_debug("exit: qlen %u",
qdf_nbuf_queue_head_qlen(&rx_thread->nbuf_queue));
return 0;
}
/**
* dp_rx_thread_gro_flush() - flush GRO packets for the RX thread
* @rx_thread - rx_thread to be processed
*
* Returns: void
*/
static void dp_rx_thread_gro_flush(struct dp_rx_thread *rx_thread)
{
dp_debug("flushing packets for thread %u", rx_thread->id);
local_bh_disable();
napi_gro_flush(&rx_thread->napi, false);
local_bh_enable();
rx_thread->stats.gro_flushes++;
}
/**
* dp_rx_thread_sub_loop() - rx thread subloop
* @rx_thread - rx_thread to be processed
* @shutdown - pointer to shutdown variable
*
* The function handles shutdown and suspend events from other
* threads and processes nbuf queue of a rx thread. In case a
* shutdown event is received from some other wlan thread, the
* function sets the shutdown pointer to true and returns
*
* Returns: 0 on success, error code on failure
*/
static int dp_rx_thread_sub_loop(struct dp_rx_thread *rx_thread, bool *shutdown)
{
while (true) {
if (qdf_atomic_test_and_clear_bit(RX_SHUTDOWN_EVENT,
&rx_thread->event_flag)) {
if (qdf_atomic_test_and_clear_bit(RX_SUSPEND_EVENT,
&rx_thread->event_flag)) {
qdf_event_set(&rx_thread->suspend_event);
}
dp_debug("shutting down (%s) id %d pid %d",
qdf_get_current_comm(), rx_thread->id,
qdf_get_current_pid());
*shutdown = true;
break;
}
dp_rx_thread_process_nbufq(rx_thread);
if (qdf_atomic_read(&rx_thread->gro_flush_ind) |
qdf_atomic_test_bit(RX_VDEV_DEL_EVENT,
&rx_thread->event_flag)) {
dp_rx_thread_gro_flush(rx_thread);
qdf_atomic_set(&rx_thread->gro_flush_ind, 0);
}
if (qdf_atomic_test_and_clear_bit(RX_VDEV_DEL_EVENT,
&rx_thread->event_flag)) {
rx_thread->stats.gro_flushes_by_vdev_del++;
qdf_event_set(&rx_thread->vdev_del_event);
}
if (qdf_atomic_test_and_clear_bit(RX_SUSPEND_EVENT,
&rx_thread->event_flag)) {
dp_debug("received suspend ind (%s) id %d pid %d",
qdf_get_current_comm(), rx_thread->id,
qdf_get_current_pid());
qdf_event_set(&rx_thread->suspend_event);
dp_debug("waiting for resume (%s) id %d pid %d",
qdf_get_current_comm(), rx_thread->id,
qdf_get_current_pid());
qdf_wait_single_event(&rx_thread->resume_event, 0);
}
break;
}
return 0;
}
/**
* dp_rx_thread_loop() - main dp rx thread loop
* @arg: pointer to dp_rx_thread structure for the rx thread
*
* Return: thread exit code
*/
static int dp_rx_thread_loop(void *arg)
{
struct dp_rx_thread *rx_thread = arg;
bool shutdown = false;
int status;
struct dp_rx_tm_handle_cmn *tm_handle_cmn;
tm_handle_cmn = rx_thread->rtm_handle_cmn;
if (!arg) {
dp_err("bad Args passed");
return 0;
}
qdf_set_user_nice(qdf_get_current_task(), -1);
qdf_set_wake_up_idle(true);
qdf_event_set(&rx_thread->start_event);
dp_info("starting rx_thread (%s) id %d pid %d", qdf_get_current_comm(),
rx_thread->id, qdf_get_current_pid());
while (!shutdown) {
/* This implements the execution model algorithm */
dp_debug("sleeping");
status =
qdf_wait_queue_interruptible
(rx_thread->wait_q,
qdf_atomic_test_bit(RX_POST_EVENT,
&rx_thread->event_flag) ||
qdf_atomic_test_bit(RX_SUSPEND_EVENT,
&rx_thread->event_flag) ||
qdf_atomic_test_bit(RX_VDEV_DEL_EVENT,
&rx_thread->event_flag));
dp_debug("woken up");
if (status == -ERESTARTSYS) {
QDF_DEBUG_PANIC("wait_event_interruptible returned -ERESTARTSYS");
break;
}
qdf_atomic_clear_bit(RX_POST_EVENT, &rx_thread->event_flag);
dp_rx_thread_sub_loop(rx_thread, &shutdown);
}
/* If we get here the scheduler thread must exit */
dp_info("exiting (%s) id %d pid %d", qdf_get_current_comm(),
rx_thread->id, qdf_get_current_pid());
qdf_event_set(&rx_thread->shutdown_event);
qdf_exit_thread(QDF_STATUS_SUCCESS);
return 0;
}
/**
* dp_rx_tm_thread_napi_poll() - dummy napi poll for rx_thread NAPI
* @napi: pointer to DP rx_thread NAPI
* @budget: NAPI BUDGET
*
* Return: 0 as it is not supposed to be polled at all as it is not scheduled.
*/
static int dp_rx_tm_thread_napi_poll(struct napi_struct *napi, int budget)
{
QDF_DEBUG_PANIC("this napi_poll should not be polled as we don't schedule it");
return 0;
}
/**
* dp_rx_tm_thread_napi_init() - Initialize dummy rx_thread NAPI
* @rx_thread: dp_rx_thread structure containing dummy napi and netdev
*
* Return: None
*/
static void dp_rx_tm_thread_napi_init(struct dp_rx_thread *rx_thread)
{
/* Todo - optimize to use only one dummy netdev for all thread napis */
init_dummy_netdev(&rx_thread->netdev);
netif_napi_add(&rx_thread->netdev, &rx_thread->napi,
dp_rx_tm_thread_napi_poll, 64);
napi_enable(&rx_thread->napi);
}
/**
* dp_rx_tm_thread_napi_deinit() - De-initialize dummy rx_thread NAPI
* @rx_thread: dp_rx_thread handle containing dummy napi and netdev
*
* Return: None
*/
static void dp_rx_tm_thread_napi_deinit(struct dp_rx_thread *rx_thread)
{
netif_napi_del(&rx_thread->napi);
}
/*
* dp_rx_tm_thread_init() - Initialize dp_rx_thread structure and thread
*
* @rx_thread: dp_rx_thread structure to be initialized
* @id: id of the thread to be initialized
*
* Return: QDF_STATUS on success, QDF error code on failure
*/
static QDF_STATUS dp_rx_tm_thread_init(struct dp_rx_thread *rx_thread,
uint8_t id)
{
char thread_name[15];
QDF_STATUS qdf_status;
qdf_mem_zero(thread_name, sizeof(thread_name));
if (!rx_thread) {
dp_err("rx_thread is null!");
return QDF_STATUS_E_FAULT;
}
rx_thread->id = id;
rx_thread->event_flag = 0;
qdf_nbuf_queue_head_init(&rx_thread->nbuf_queue);
qdf_event_create(&rx_thread->start_event);
qdf_event_create(&rx_thread->suspend_event);
qdf_event_create(&rx_thread->resume_event);
qdf_event_create(&rx_thread->shutdown_event);
qdf_event_create(&rx_thread->vdev_del_event);
qdf_atomic_init(&rx_thread->gro_flush_ind);
qdf_init_waitqueue_head(&rx_thread->wait_q);
qdf_scnprintf(thread_name, sizeof(thread_name), "dp_rx_thread_%u", id);
dp_info("%s %u", thread_name, id);
if (cdp_cfg_get(dp_rx_tm_get_soc_handle(rx_thread->rtm_handle_cmn),
cfg_dp_gro_enable))
dp_rx_tm_thread_napi_init(rx_thread);
rx_thread->task = qdf_create_thread(dp_rx_thread_loop,
rx_thread, thread_name);
if (!rx_thread->task) {
dp_err("could not create dp_rx_thread %d", id);
return QDF_STATUS_E_FAILURE;
}
qdf_wake_up_process(rx_thread->task);
qdf_status = qdf_wait_single_event(&rx_thread->start_event, 0);
if (!QDF_IS_STATUS_SUCCESS(qdf_status)) {
dp_err("failed waiting for thread creation id %d", id);
return QDF_STATUS_E_FAILURE;
}
return QDF_STATUS_SUCCESS;
}
/*
* dp_rx_tm_thread_deinit() - De-Initialize dp_rx_thread structure and thread
* @rx_thread: dp_rx_thread structure to be de-initialized
* @id: id of the thread to be initialized
*
* Return: QDF_STATUS_SUCCESS
*/
static QDF_STATUS dp_rx_tm_thread_deinit(struct dp_rx_thread *rx_thread)
{
qdf_event_destroy(&rx_thread->start_event);
qdf_event_destroy(&rx_thread->suspend_event);
qdf_event_destroy(&rx_thread->resume_event);
qdf_event_destroy(&rx_thread->shutdown_event);
qdf_event_destroy(&rx_thread->vdev_del_event);
if (cdp_cfg_get(dp_rx_tm_get_soc_handle(rx_thread->rtm_handle_cmn),
cfg_dp_gro_enable))
dp_rx_tm_thread_napi_deinit(rx_thread);
return QDF_STATUS_SUCCESS;
}
QDF_STATUS dp_rx_tm_init(struct dp_rx_tm_handle *rx_tm_hdl,
uint8_t num_dp_rx_threads)
{
int i;
QDF_STATUS qdf_status = QDF_STATUS_SUCCESS;
if (num_dp_rx_threads > DP_MAX_RX_THREADS) {
dp_err("unable to initialize %u number of threads. MAX %u",
num_dp_rx_threads, DP_MAX_RX_THREADS);
return QDF_STATUS_E_INVAL;
}
rx_tm_hdl->num_dp_rx_threads = num_dp_rx_threads;
rx_tm_hdl->state = DP_RX_THREADS_INVALID;
dp_info("initializing %u threads", num_dp_rx_threads);
/* allocate an array to contain the DP RX thread pointers */
rx_tm_hdl->rx_thread = qdf_mem_malloc(num_dp_rx_threads *
sizeof(struct dp_rx_thread *));
if (qdf_unlikely(!rx_tm_hdl->rx_thread)) {
qdf_status = QDF_STATUS_E_NOMEM;
goto ret;
}
for (i = 0; i < rx_tm_hdl->num_dp_rx_threads; i++) {
rx_tm_hdl->rx_thread[i] =
(struct dp_rx_thread *)
qdf_mem_malloc(sizeof(struct dp_rx_thread));
if (qdf_unlikely(!rx_tm_hdl->rx_thread[i])) {
QDF_ASSERT(0);
qdf_status = QDF_STATUS_E_NOMEM;
goto ret;
}
rx_tm_hdl->rx_thread[i]->rtm_handle_cmn =
(struct dp_rx_tm_handle_cmn *)rx_tm_hdl;
qdf_status =
dp_rx_tm_thread_init(rx_tm_hdl->rx_thread[i], i);
if (!QDF_IS_STATUS_SUCCESS(qdf_status))
break;
}
ret:
if (!QDF_IS_STATUS_SUCCESS(qdf_status))
dp_rx_tm_deinit(rx_tm_hdl);
else
rx_tm_hdl->state = DP_RX_THREADS_RUNNING;
return qdf_status;
}
/**
* dp_rx_tm_resume() - suspend DP RX threads
* @rx_tm_hdl: dp_rx_tm_handle containing the overall thread
* infrastructure
*
* Return: Success/Failure
*/
QDF_STATUS dp_rx_tm_suspend(struct dp_rx_tm_handle *rx_tm_hdl)
{
int i;
QDF_STATUS qdf_status;
struct dp_rx_thread *rx_thread;
if (rx_tm_hdl->state == DP_RX_THREADS_SUSPENDED) {
dp_info("already in suspend state! Ignoring.");
return QDF_STATUS_E_INVAL;
}
rx_tm_hdl->state = DP_RX_THREADS_SUSPENDING;
for (i = 0; i < rx_tm_hdl->num_dp_rx_threads; i++) {
if (!rx_tm_hdl->rx_thread[i])
continue;
qdf_event_reset(&rx_tm_hdl->rx_thread[i]->resume_event);
qdf_event_reset(&rx_tm_hdl->rx_thread[i]->suspend_event);
qdf_set_bit(RX_SUSPEND_EVENT,
&rx_tm_hdl->rx_thread[i]->event_flag);
qdf_wake_up_interruptible(&rx_tm_hdl->rx_thread[i]->wait_q);
}
for (i = 0; i < rx_tm_hdl->num_dp_rx_threads; i++) {
rx_thread = rx_tm_hdl->rx_thread[i];
if (!rx_thread)
continue;
dp_debug("thread %d", i);
qdf_status = qdf_wait_single_event(&rx_thread->suspend_event,
DP_RX_THREAD_WAIT_TIMEOUT);
if (QDF_IS_STATUS_SUCCESS(qdf_status))
dp_debug("thread:%d suspended", rx_thread->id);
else
goto suspend_fail;
}
rx_tm_hdl->state = DP_RX_THREADS_SUSPENDED;
return QDF_STATUS_SUCCESS;
suspend_fail:
dp_err("thread:%d %s(%d) while waiting for suspend",
rx_thread->id,
qdf_status == QDF_STATUS_E_TIMEOUT ? "timeout out" : "failed",
qdf_status);
dp_rx_tm_resume(rx_tm_hdl);
return qdf_status;
}
/**
* dp_rx_thread_flush_by_vdev_id() - flush rx packets by vdev_id in
a particular rx thread queue
* @rx_thread - rx_thread pointer of the queue from which packets are
* to be flushed out
* @vdev_id: vdev id for which packets are to be flushed
*
* The function will flush the RX packets by vdev_id in a particular
* RX thead queue. And will notify and wait the TX thread to flush the
* packets in the NAPI RX GRO hash list
*
* Return: void
*/
static inline
void dp_rx_thread_flush_by_vdev_id(struct dp_rx_thread *rx_thread,
uint8_t vdev_id)
{
qdf_nbuf_t nbuf_list, tmp_nbuf_list;
uint32_t num_list_elements = 0;
QDF_STATUS qdf_status;
qdf_nbuf_queue_head_lock(&rx_thread->nbuf_queue);
QDF_NBUF_QUEUE_WALK_SAFE(&rx_thread->nbuf_queue, nbuf_list,
tmp_nbuf_list) {
if (QDF_NBUF_CB_RX_VDEV_ID(nbuf_list) == vdev_id) {
qdf_nbuf_unlink_no_lock(nbuf_list,
&rx_thread->nbuf_queue);
dp_rx_thread_adjust_nbuf_list(nbuf_list);
num_list_elements =
QDF_NBUF_CB_RX_NUM_ELEMENTS_IN_LIST(nbuf_list);
rx_thread->stats.rx_flushed += num_list_elements;
qdf_nbuf_list_free(nbuf_list);
}
}
qdf_nbuf_queue_head_unlock(&rx_thread->nbuf_queue);
qdf_set_bit(RX_VDEV_DEL_EVENT, &rx_thread->event_flag);
qdf_wake_up_interruptible(&rx_thread->wait_q);
qdf_status = qdf_wait_single_event(&rx_thread->vdev_del_event,
DP_RX_THREAD_WAIT_TIMEOUT);
if (QDF_IS_STATUS_SUCCESS(qdf_status))
dp_debug("thread:%d napi gro flush successfully",
rx_thread->id);
else if (qdf_status == QDF_STATUS_E_TIMEOUT)
dp_err("thread:%d timed out waiting for napi gro flush",
rx_thread->id);
else
dp_err("thread:%d failed while waiting for napi gro flush",
rx_thread->id);
}
/**
* dp_rx_tm_flush_by_vdev_id() - flush rx packets by vdev_id in all
rx thread queues
* @rx_tm_hdl: dp_rx_tm_handle containing the overall thread
* infrastructure
* @vdev_id: vdev id for which packets are to be flushed
*
* Return: QDF_STATUS_SUCCESS
*/
QDF_STATUS dp_rx_tm_flush_by_vdev_id(struct dp_rx_tm_handle *rx_tm_hdl,
uint8_t vdev_id)
{
struct dp_rx_thread *rx_thread;
int i;
for (i = 0; i < rx_tm_hdl->num_dp_rx_threads; i++) {
rx_thread = rx_tm_hdl->rx_thread[i];
if (!rx_thread)
continue;
dp_debug("thread %d", i);
dp_rx_thread_flush_by_vdev_id(rx_thread, vdev_id);
}
return QDF_STATUS_SUCCESS;
}
/**
* dp_rx_tm_resume() - resume DP RX threads
* @rx_tm_hdl: dp_rx_tm_handle containing the overall thread
* infrastructure
*
* Return: QDF_STATUS_SUCCESS on resume success. QDF error otherwise.
*/
QDF_STATUS dp_rx_tm_resume(struct dp_rx_tm_handle *rx_tm_hdl)
{
int i;
if (rx_tm_hdl->state != DP_RX_THREADS_SUSPENDED &&
rx_tm_hdl->state != DP_RX_THREADS_SUSPENDING) {
dp_info("resume callback received w/o suspend! Ignoring.");
return QDF_STATUS_E_INVAL;
}
for (i = 0; i < rx_tm_hdl->num_dp_rx_threads; i++) {
if (!rx_tm_hdl->rx_thread[i])
continue;
dp_debug("calling thread %d to resume", i);
/* postively reset event_flag for DP_RX_THREADS_SUSPENDING
* state
*/
qdf_clear_bit(RX_SUSPEND_EVENT,
&rx_tm_hdl->rx_thread[i]->event_flag);
qdf_event_set(&rx_tm_hdl->rx_thread[i]->resume_event);
}
rx_tm_hdl->state = DP_RX_THREADS_RUNNING;
return QDF_STATUS_SUCCESS;
}
/**
* dp_rx_tm_shutdown() - shutdown all DP RX threads
* @rx_tm_hdl: dp_rx_tm_handle containing the overall thread infrastructure
*
* Return: QDF_STATUS_SUCCESS
*/
static QDF_STATUS dp_rx_tm_shutdown(struct dp_rx_tm_handle *rx_tm_hdl)
{
int i;
for (i = 0; i < rx_tm_hdl->num_dp_rx_threads; i++) {
if (!rx_tm_hdl->rx_thread[i])
continue;
qdf_set_bit(RX_SHUTDOWN_EVENT,
&rx_tm_hdl->rx_thread[i]->event_flag);
qdf_set_bit(RX_POST_EVENT,
&rx_tm_hdl->rx_thread[i]->event_flag);
qdf_wake_up_interruptible(&rx_tm_hdl->rx_thread[i]->wait_q);
}
for (i = 0; i < rx_tm_hdl->num_dp_rx_threads; i++) {
if (!rx_tm_hdl->rx_thread[i])
continue;
dp_debug("waiting for shutdown of thread %d", i);
qdf_wait_single_event(&rx_tm_hdl->rx_thread[i]->shutdown_event,
0);
}
rx_tm_hdl->state = DP_RX_THREADS_INVALID;
return QDF_STATUS_SUCCESS;
}
/**
* dp_rx_tm_deinit() - de-initialize RX thread infrastructure
* @rx_tm_hdl: dp_rx_tm_handle containing the overall thread
* infrastructure
*
* Return: QDF_STATUS_SUCCESS
*/
QDF_STATUS dp_rx_tm_deinit(struct dp_rx_tm_handle *rx_tm_hdl)
{
int i = 0;
if (!rx_tm_hdl->rx_thread) {
dp_err("rx_tm_hdl->rx_thread not initialized!");
return QDF_STATUS_SUCCESS;
}
dp_rx_tm_shutdown(rx_tm_hdl);
for (i = 0; i < rx_tm_hdl->num_dp_rx_threads; i++) {
if (!rx_tm_hdl->rx_thread[i])
continue;
dp_rx_tm_thread_deinit(rx_tm_hdl->rx_thread[i]);
qdf_mem_free(rx_tm_hdl->rx_thread[i]);
}
/* free the array of RX thread pointers*/
qdf_mem_free(rx_tm_hdl->rx_thread);
rx_tm_hdl->rx_thread = NULL;
return QDF_STATUS_SUCCESS;
}
/**
* dp_rx_tm_select_thread() - select a DP RX thread for a nbuf
* @rx_tm_hdl: dp_rx_tm_handle containing the overall thread
* infrastructure
* @reo_ring_num: REO ring number corresponding to the thread
*
* The function relies on the presence of QDF_NBUF_CB_RX_CTX_ID passed to it
* from the nbuf list. Depending on the RX_CTX (copy engine or reo
* ring) on which the packet was received, the function selects
* a corresponding rx_thread.
*
* Return: rx thread ID selected for the nbuf
*/
static uint8_t dp_rx_tm_select_thread(struct dp_rx_tm_handle *rx_tm_hdl,
uint8_t reo_ring_num)
{
uint8_t selected_rx_thread;
if (reo_ring_num >= rx_tm_hdl->num_dp_rx_threads) {
dp_err_rl("unexpected ring number");
QDF_BUG(0);
return 0;
}
selected_rx_thread = reo_ring_num;
dp_debug("selected thread %u", selected_rx_thread);
return selected_rx_thread;
}
QDF_STATUS dp_rx_tm_enqueue_pkt(struct dp_rx_tm_handle *rx_tm_hdl,
qdf_nbuf_t nbuf_list)
{
uint8_t selected_thread_id;
selected_thread_id =
dp_rx_tm_select_thread(rx_tm_hdl,
QDF_NBUF_CB_RX_CTX_ID(nbuf_list));
dp_rx_tm_thread_enqueue(rx_tm_hdl->rx_thread[selected_thread_id],
nbuf_list);
return QDF_STATUS_SUCCESS;
}
QDF_STATUS
dp_rx_tm_gro_flush_ind(struct dp_rx_tm_handle *rx_tm_hdl, int rx_ctx_id)
{
uint8_t selected_thread_id;
selected_thread_id = dp_rx_tm_select_thread(rx_tm_hdl, rx_ctx_id);
dp_rx_tm_thread_gro_flush_ind(rx_tm_hdl->rx_thread[selected_thread_id]);
return QDF_STATUS_SUCCESS;
}
struct napi_struct *dp_rx_tm_get_napi_context(struct dp_rx_tm_handle *rx_tm_hdl,
uint8_t rx_ctx_id)
{
if (rx_ctx_id >= rx_tm_hdl->num_dp_rx_threads) {
dp_err_rl("unexpected rx_ctx_id %u", rx_ctx_id);
QDF_BUG(0);
return NULL;
}
return &rx_tm_hdl->rx_thread[rx_ctx_id]->napi;
}
QDF_STATUS dp_rx_tm_set_cpu_mask(struct dp_rx_tm_handle *rx_tm_hdl,
qdf_cpu_mask *new_mask)
{
int i = 0;
for (i = 0; i < rx_tm_hdl->num_dp_rx_threads; i++) {
if (!rx_tm_hdl->rx_thread[i])
continue;
qdf_thread_set_cpus_allowed_mask(rx_tm_hdl->rx_thread[i]->task,
new_mask);
}
return QDF_STATUS_SUCCESS;
}