/* * Broadcom Dongle Host Driver (DHD), Linux-specific network interface * Selected code segments for XR * * Portions of this code are copyright (c) 2023 Cypress Semiconductor Corporation, * an Infineon company * * This program is the proprietary software of infineon and/or * its licensors, and may only be used, duplicated, modified or distributed * pursuant to the terms and conditions of a separate, written license * agreement executed between you and infineon (an "Authorized License"). * Except as set forth in an Authorized License, infineon grants no license * (express or implied), right to use, or waiver of any kind with respect to * the Software, and infineon expressly reserves all rights in and to the * Software and all intellectual property rights therein. IF YOU HAVE NO * AUTHORIZED LICENSE, THEN YOU HAVE NO RIGHT TO USE THIS SOFTWARE IN ANY * WAY, AND SHOULD IMMEDIATELY NOTIFY INFINEON AND DISCONTINUE ALL USE OF * THE SOFTWARE. * * Except as expressly set forth in the Authorized License, * * 1. This program, including its structure, sequence and organization, * constitutes the valuable trade secrets of infineon, and you shall use * all reasonable efforts to protect the confidentiality thereof, and to * use this information only in connection with your use of infineon * integrated circuit products. * * 2. TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED * "AS IS" AND WITH ALL FAULTS AND INFINEON MAKES NO PROMISES, * REPRESENTATIONS OR WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR * OTHERWISE, WITH RESPECT TO THE SOFTWARE. INFINEON SPECIFICALLY * DISCLAIMS ANY AND ALL IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, * NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF VIRUSES, * ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR * CORRESPONDENCE TO DESCRIPTION. YOU ASSUME THE ENTIRE RISK ARISING * OUT OF USE OR PERFORMANCE OF THE SOFTWARE. * * 3. TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL * INFINEON OR ITS LICENSORS BE LIABLE FOR (i) CONSEQUENTIAL, INCIDENTAL, * SPECIAL, INDIRECT, OR EXEMPLARY DAMAGES WHATSOEVER ARISING OUT OF OR * IN ANY WAY RELATING TO YOUR USE OF OR INABILITY TO USE THE SOFTWARE EVEN * IF INFINEON HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES; OR (ii) * ANY AMOUNT IN EXCESS OF THE AMOUNT ACTUALLY PAID FOR THE SOFTWARE ITSELF * OR U.S. $1, WHICHEVER IS GREATER. THESE LIMITATIONS SHALL APPLY * NOTWITHSTANDING ANY FAILURE OF ESSENTIAL PURPOSE OF ANY LIMITED REMEDY. * * * <> * * $Id$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_ADAPTIVE_SCHED #include #endif /* ENABLE_ADAPTIVE_SCHED */ #include #include #include #include #include #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)) #include #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */ #include #include #include #include #include #include #include #include #include <802.3.h> #include #include #include #include #include #include #include #include #include #if defined(WL_CFG80211) #include #endif /* WL_CFG80211 */ #ifdef WL_DHD_XR #include #endif /* WL_DHD_XR */ #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)) #include #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */ #include #ifdef WL_DHD_XR /* XR cmd thread priority */ int dhd_xr_cmd_prio = CUSTOM_XR_CMD_PRIO_SETTING; module_param(dhd_xr_cmd_prio, int, 0); int dhd_send_xr_cmd(dhd_pub_t *dest_dhdp, void *xr_cmd, int len, struct completion *comp, bool sync); static void dhd_os_xrcmdlock(dhd_pub_t *pub); static void dhd_os_xrcmdunlock(dhd_pub_t *pub); static inline void dhd_xr_cmd_clr_store_buf(dhd_pub_t *dhdp) { uint32 store_idx; xr_buf_t *xr_buf = NULL; xr_ctx_t *xr_ctx = NULL; if (!dhdp) { DHD_ERROR(("%s: NULL dhdp!!!\n", __func__)); return; } xr_ctx = (xr_ctx_t *) DHD_GET_XR_CTX(dhdp); dhd_os_xrcmdlock(dhdp); store_idx = xr_ctx->xr_cmd_store_idx; xr_buf = (xr_buf_t *) &xr_ctx->xr_cmd_buf[store_idx]; if (xr_buf->in_use) { bzero(&xr_ctx->xr_cmd_buf[store_idx], sizeof(xr_buf_t)); xr_ctx->xr_cmd_buf[store_idx].in_use = FALSE; xr_ctx->xr_cmd_buf[store_idx].sync = FALSE; xr_buf->in_use = FALSE; xr_buf->sync = FALSE; DHD_ERROR(("%s:flush cmdbuf %p, store idx %d\n", __func__, xr_buf, store_idx)); } dhd_os_xrcmdunlock(dhdp); return; } static inline int dhd_xr_cmd_enqueue(dhd_pub_t *dhdp, void *cmd, int len, bool sync) { uint32 store_idx; uint32 sent_idx; xr_buf_t *xr_buf = NULL; xr_ctx_t *xr_ctx = NULL; if (!dhdp) { DHD_ERROR(("%s: NULL dhdp!!!\n", __func__)); return BCME_ERROR; } xr_ctx = (xr_ctx_t *) DHD_GET_XR_CTX(dhdp); if (!cmd) { DHD_ERROR(("%s: NULL cmd!!!\n", __func__)); return BCME_ERROR; } dhd_os_xrcmdlock(dhdp); store_idx = xr_ctx->xr_cmd_store_idx; sent_idx = xr_ctx->xr_cmd_sent_idx; xr_buf = (xr_buf_t *) &xr_ctx->xr_cmd_buf[store_idx]; if (xr_buf->in_use) { dhd_os_xrcmdunlock(dhdp); dhd_xr_cmd_clr_store_buf(dhdp); dhd_os_xrcmdlock(dhdp); DHD_TRACE(("%s:clear stale cmdbuf %p, store idx %d sent idx %d\n", __func__, cmd, store_idx, sent_idx)); } DHD_TRACE(("%s: Store cmd %p. idx %d -> %d\n", __func__, cmd, store_idx, (store_idx + 1) & (MAX_XR_CMD_NUM - 1))); xr_buf->in_use = TRUE; if (sync) xr_buf->sync = sync; xr_buf->len = len; bcopy((char *) cmd, (char *)&xr_buf->buf, xr_buf->len); xr_ctx->xr_cmd_store_idx = (store_idx + 1) & (MAX_XR_CMD_NUM - 1); dhd_os_xrcmdunlock(dhdp); return BCME_OK; } static inline int dhd_xr_cmd_dequeue(dhd_pub_t *dhdp, xr_buf_t *xr_buf) { uint32 store_idx; uint32 sent_idx; int ret = BCME_OK; xr_buf_t *curr_buf; xr_ctx_t *xr_ctx = NULL; if (!dhdp) { DHD_ERROR(("%s: NULL dhdp!!!\n", __func__)); return BCME_ERROR; } xr_ctx = (xr_ctx_t *) DHD_GET_XR_CTX(dhdp); if (!xr_buf) { DHD_ERROR(("%s:xr_buf is null\n", __func__)); return BCME_ERROR; } dhd_os_xrcmdlock(dhdp); store_idx = xr_ctx->xr_cmd_store_idx; sent_idx = xr_ctx->xr_cmd_sent_idx; curr_buf = &xr_ctx->xr_cmd_buf[sent_idx]; bcopy(curr_buf, xr_buf, sizeof(xr_buf_t)); bzero(&xr_ctx->xr_cmd_buf[sent_idx], sizeof(xr_buf_t)); xr_ctx->xr_cmd_buf[sent_idx].in_use = FALSE; xr_ctx->xr_cmd_buf[sent_idx].sync = FALSE; curr_buf->in_use = FALSE; curr_buf->sync = FALSE; xr_ctx->xr_cmd_sent_idx = (sent_idx + 1) & (MAX_XR_CMD_NUM - 1); if (xr_buf->in_use == FALSE) { dhd_os_xrcmdunlock(dhdp); DHD_ERROR(("%s: Dequeued cmd is NULL, store idx %d sent idx %d\n", __func__, store_idx, sent_idx)); return BCME_ERROR; } DHD_TRACE(("%s:xr_buf(%p), sent idx %d\n", __func__, xr_buf, sent_idx)); dhd_os_xrcmdunlock(dhdp); return ret; } int dhd_xr_cmd_thread(void *data) { tsk_ctl_t *tsk = (tsk_ctl_t *)data; dhd_info_t *dhd = (dhd_info_t *)tsk->parent; dhd_pub_t *pub = &dhd->pub; int ret = BCME_OK; /* This thread doesn't need any user-level access, * so get rid of all our resources */ if (dhd_xr_cmd_prio > 0) { struct sched_param param; param.sched_priority = (dhd_xr_cmd_prio < MAX_RT_PRIO)?dhd_xr_cmd_prio:(MAX_RT_PRIO-1); setScheduler(current, SCHED_FIFO, ¶m); } #ifdef CUSTOM_SET_CPUCORE dhd->pub.current_xr_cmd = current; #endif /* CUSTOM_SET_CPUCORE */ /* Run until signal received */ while (1) { if (down_interruptible(&tsk->sema) == 0) { xr_buf_t *xr_buf = NULL; /* * xr_buf is freed by xr_cmd_handler,if cmd is handled * in wq xr_buf is freed in the wq handler. */ xr_buf = (xr_buf_t *)kzalloc(sizeof(xr_buf_t), GFP_KERNEL); #ifdef ENABLE_ADAPTIVE_SCHED dhd_sched_policy(dhd_xr_cmd_prio); #endif /* ENABLE_ADAPTIVE_SCHED */ SMP_RD_BARRIER_DEPENDS(); if (tsk->terminated) { break; } ret = dhd_xr_cmd_dequeue(pub, xr_buf); if (ret != BCME_OK) { DHD_ERROR(("%s:Dequeue failed\n", __func__)); if (xr_buf) kfree(xr_buf); continue; } else { ret = xr_cmd_handler(pub, xr_buf); if (ret != BCME_OK) { DHD_ERROR(("xr_cmd_handler failure\n")); continue; } /* * xr_buf is handled by xr_cmd_handler so we need * to make sure xr_buf is not dangling. */ xr_buf = NULL; } DHD_OS_WAKE_UNLOCK(pub); } else { break; } } complete_and_exit(&tsk->completed, 0); } int dhd_send_xr_cmd(dhd_pub_t *dest_dhdp, void *xr_cmd, int len, struct completion *comp, bool sync) { dhd_info_t *dhd = (dhd_info_t *)dest_dhdp->info; int ret = BCME_OK; if (sync) { if (comp == NULL) { DHD_ERROR(("%s:sync command but completion variable is null\n", __func__)); ret = BCME_ERROR; goto end; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) init_completion(comp); #else /* reinitialize completion to clear previous count */ INIT_COMPLETION(comp); #endif // endif } DHD_OS_WAKE_LOCK(dest_dhdp); ret = dhd_xr_cmd_enqueue(dest_dhdp, xr_cmd, len, sync); if (ret == BCME_ERROR) { DHD_ERROR(("send cmd retry failed\n")); DHD_OS_WAKE_UNLOCK(dest_dhdp); goto end; } else { if (dhd->thr_xr_cmd_ctl.thr_pid >= 0) { up(&dhd->thr_xr_cmd_ctl.sema); } } if (sync) { if (wait_for_completion_timeout(comp, msecs_to_jiffies(2000)) == 0) { DHD_ERROR(("dhd_send_xr_cmd: timeout occurred\n")); ret = BCME_ERROR; DHD_OS_WAKE_UNLOCK(dest_dhdp); goto end; } } end: return ret; } void dhd_deferred_work_xr_cmd_handler(void *handle, void *event_info, u8 event) { dhd_info_t *dhd_info = handle; dhd_pub_t *dhd = &dhd_info->pub; xr_buf_t *xr_buf = (xr_buf_t *) event_info; int ret = BCME_OK; if (event != DHD_WQ_WORK_XR_CMD_HANDLER) { DHD_ERROR(("%s: unexpected event \n", __FUNCTION__)); goto fail; } ret = xr_cmd_deferred_handler(dhd, xr_buf); if (ret != BCME_OK) { DHD_ERROR(("xr_cmd_deferred_handler failure\n")); } fail: if (xr_buf) kfree(xr_buf); return; } /* dhd_wq_xr_cmd_handler handles xr cmd through a workq * which allows the xr_cmd_thread to unblock. */ void dhd_wq_xr_cmd_handler(dhd_pub_t *dhdp, void *info) { dhd_info_t *dhd_info = dhdp->info; dhd_deferred_schedule_work(dhd_info->dhd_deferred_wq, (void *)info, DHD_WQ_WORK_XR_CMD_HANDLER, dhd_deferred_work_xr_cmd_handler, DHD_WQ_WORK_PRIORITY_LOW); return; } static void dhd_os_xrcmdlock(dhd_pub_t *pub) { dhd_info_t *dhd; dhd = (dhd_info_t *)(pub->info); spin_lock_bh(&dhd->xr_cmd_lock); } static void dhd_os_xrcmdunlock(dhd_pub_t *pub) { dhd_info_t *dhd; dhd = (dhd_info_t *)(pub->info); spin_unlock_bh(&dhd->xr_cmd_lock); } #endif /* WL_DHD_XR */