/* * --------------------------------------------------------------------------- * FILE: bh.c * * PURPOSE: * Provides an implementation for the driver bottom-half. * It is part of the porting exercise in Linux. * * Copyright (C) 2005-2009 by Cambridge Silicon Radio Ltd. * * Refer to LICENSE.txt included with this source code for details on * the license terms. * * --------------------------------------------------------------------------- */ #include "csr_wifi_hip_unifi.h" #include "unifi_priv.h" #include <linux/sched/rt.h> /* * --------------------------------------------------------------------------- * uf_start_thread * * Helper function to start a new thread. * * Arguments: * priv Pointer to OS driver structure for the device. * thread Pointer to the thread object * func The thread function * * Returns: * 0 on success or else a Linux error code. * --------------------------------------------------------------------------- */ int uf_start_thread(unifi_priv_t *priv, struct uf_thread *thread, int (*func)(void *)) { if (thread->thread_task != NULL) { unifi_error(priv, "%s thread already started\n", thread->name); return 0; } /* Start the kernel thread that handles all h/w accesses. */ thread->thread_task = kthread_run(func, priv, "%s", thread->name); if (IS_ERR(thread->thread_task)) return PTR_ERR(thread->thread_task); /* Module parameter overides the thread priority */ if (bh_priority != -1) { if (bh_priority >= 0 && bh_priority <= MAX_RT_PRIO) { struct sched_param param; priv->bh_thread.prio = bh_priority; unifi_trace(priv, UDBG1, "%s thread (RT) priority = %d\n", thread->name, bh_priority); param.sched_priority = bh_priority; sched_setscheduler(thread->thread_task, SCHED_FIFO, ¶m); } else if (bh_priority > MAX_RT_PRIO && bh_priority <= MAX_PRIO) { priv->bh_thread.prio = bh_priority; unifi_trace(priv, UDBG1, "%s thread priority = %d\n", thread->name, PRIO_TO_NICE(bh_priority)); set_user_nice(thread->thread_task, PRIO_TO_NICE(bh_priority)); } else { priv->bh_thread.prio = DEFAULT_PRIO; unifi_warning(priv, "%s thread unsupported (%d) priority\n", thread->name, bh_priority); } } else priv->bh_thread.prio = DEFAULT_PRIO; unifi_trace(priv, UDBG2, "Started %s thread\n", thread->name); return 0; } /* uf_start_thread() */ /* * --------------------------------------------------------------------------- * uf_stop_thread * * Helper function to stop a thread. * * Arguments: * priv Pointer to OS driver structure for the device. * thread Pointer to the thread object * * Returns: * * --------------------------------------------------------------------------- */ void uf_stop_thread(unifi_priv_t *priv, struct uf_thread *thread) { if (!thread->thread_task) { unifi_notice(priv, "%s thread is already stopped\n", thread->name); return; } unifi_trace(priv, UDBG2, "Stopping %s thread\n", thread->name); kthread_stop(thread->thread_task); thread->thread_task = NULL; } /* uf_stop_thread() */ /* * --------------------------------------------------------------------------- * uf_wait_for_thread_to_stop * * Helper function to wait until a thread is stopped. * * Arguments: * priv Pointer to OS driver structure for the device. * * Returns: * * --------------------------------------------------------------------------- */ void uf_wait_for_thread_to_stop(unifi_priv_t *priv, struct uf_thread *thread) { /* * kthread_stop() cannot handle the thread exiting while * kthread_should_stop() is false, so sleep until kthread_stop() * wakes us up */ unifi_trace(priv, UDBG2, "%s waiting for the stop signal.\n", thread->name); set_current_state(TASK_INTERRUPTIBLE); if (!kthread_should_stop()) { unifi_trace(priv, UDBG2, "%s schedule....\n", thread->name); schedule(); } thread->thread_task = NULL; unifi_trace(priv, UDBG2, "%s exiting....\n", thread->name); } /* uf_wait_for_thread_to_stop() */ /* * --------------------------------------------------------------------------- * handle_bh_error * * This function reports an error returned from the HIP core bottom-half. * Normally, implemented during the porting exercise, passing the error * to the SME using unifi_sys_wifi_off_ind(). * The SME will try to reset the device and go through * the initialisation of the UniFi. * * Arguments: * priv Pointer to OS driver structure for the device. * * Returns: * None. * --------------------------------------------------------------------------- */ static void handle_bh_error(unifi_priv_t *priv) { netInterface_priv_t *interfacePriv; u8 conf_param = CONFIG_IND_ERROR; u8 interfaceTag; /* Block unifi_run_bh() until the error has been handled. */ priv->bh_thread.block_thread = 1; /* Consider UniFi to be uninitialised */ priv->init_progress = UNIFI_INIT_NONE; /* Stop the network traffic */ for (interfaceTag = 0; interfaceTag < CSR_WIFI_NUM_INTERFACES; interfaceTag++) { interfacePriv = priv->interfacePriv[interfaceTag]; if (interfacePriv->netdev_registered) netif_carrier_off(priv->netdev[interfaceTag]); } #ifdef CSR_NATIVE_LINUX /* Force any client waiting on an mlme_wait_for_reply() to abort. */ uf_abort_mlme(priv); /* Cancel any pending workqueue tasks */ flush_workqueue(priv->unifi_workqueue); #endif /* CSR_NATIVE_LINUX */ unifi_error(priv, "handle_bh_error: fatal error is reported to the SME.\n"); /* Notify the clients (SME or unifi_manager) for the error. */ ul_log_config_ind(priv, &conf_param, sizeof(u8)); } /* handle_bh_error() */ /* * --------------------------------------------------------------------------- * bh_thread_function * * All hardware access happens in this thread. * This means there is no need for locks on the hardware and we don't need * to worry about reentrancy with the SDIO library. * Provides and example implementation on how to call unifi_bh(), which * is part of the HIP core API. * * It processes the events generated by unifi_run_bh() to serialise calls * to unifi_bh(). It also demonstrates how the timeout parameter passed in * and returned from unifi_bh() needs to be handled. * * Arguments: * arg Pointer to OS driver structure for the device. * * Returns: * None. * * Notes: * When the bottom half of the driver needs to process signals, events, * or simply the host status (i.e sleep mode), it invokes unifi_run_bh(). * Since we need all SDIO transaction to be in a single thread, the * unifi_run_bh() will wake up this thread to process it. * * --------------------------------------------------------------------------- */ static int bh_thread_function(void *arg) { unifi_priv_t *priv = (unifi_priv_t *)arg; CsrResult csrResult; long ret; u32 timeout, t; struct uf_thread *this_thread; unifi_trace(priv, UDBG2, "bh_thread_function starting\n"); this_thread = &priv->bh_thread; t = timeout = 0; while (!kthread_should_stop()) { /* wait until an error occurs, or we need to process something. */ unifi_trace(priv, UDBG3, "bh_thread goes to sleep.\n"); if (timeout > 0) { /* Convert t in ms to jiffies */ t = msecs_to_jiffies(timeout); ret = wait_event_interruptible_timeout(this_thread->wakeup_q, (this_thread->wakeup_flag && !this_thread->block_thread) || kthread_should_stop(), t); timeout = (ret > 0) ? jiffies_to_msecs(ret) : 0; } else { ret = wait_event_interruptible(this_thread->wakeup_q, (this_thread->wakeup_flag && !this_thread->block_thread) || kthread_should_stop()); } if (kthread_should_stop()) { unifi_trace(priv, UDBG2, "bh_thread: signalled to exit\n"); break; } if (ret < 0) { unifi_notice(priv, "bh_thread: wait_event returned %d, thread will exit\n", ret); uf_wait_for_thread_to_stop(priv, this_thread); break; } this_thread->wakeup_flag = 0; unifi_trace(priv, UDBG3, "bh_thread calls unifi_bh().\n"); CsrSdioClaim(priv->sdio); csrResult = unifi_bh(priv->card, &timeout); if(csrResult != CSR_RESULT_SUCCESS) { if (csrResult == CSR_WIFI_HIP_RESULT_NO_DEVICE) { CsrSdioRelease(priv->sdio); uf_wait_for_thread_to_stop(priv, this_thread); break; } /* Errors must be delivered to the error task */ handle_bh_error(priv); } CsrSdioRelease(priv->sdio); } /* * I would normally try to call csr_sdio_remove_irq() here to make sure * that we do not get any interrupts while this thread is not running. * However, the MMC/SDIO driver tries to kill its' interrupt thread. * The kernel threads implementation does not allow to kill threads * from a signalled to stop thread. * So, instead call csr_sdio_linux_remove_irq() always after calling * uf_stop_thread() to kill this thread. */ unifi_trace(priv, UDBG2, "bh_thread exiting....\n"); return 0; } /* bh_thread_function() */ /* * --------------------------------------------------------------------------- * uf_init_bh * * Helper function to start the bottom half of the driver. * All we need to do here is start the I/O bh thread. * * Arguments: * priv Pointer to OS driver structure for the device. * * Returns: * 0 on success or else a Linux error code. * --------------------------------------------------------------------------- */ int uf_init_bh(unifi_priv_t *priv) { int r; /* Enable mlme interface. */ priv->io_aborted = 0; /* Start the BH thread */ r = uf_start_thread(priv, &priv->bh_thread, bh_thread_function); if (r) { unifi_error(priv, "uf_init_bh: failed to start the BH thread.\n"); return r; } /* Allow interrupts */ r = csr_sdio_linux_install_irq(priv->sdio); if (r) { unifi_error(priv, "uf_init_bh: failed to install the IRQ.\n"); uf_stop_thread(priv, &priv->bh_thread); } return r; } /* uf_init_bh() */ /* * --------------------------------------------------------------------------- * unifi_run_bh * * Part of the HIP core lib API, implemented in the porting exercise. * The bottom half of the driver calls this function when * it wants to process anything that requires access to unifi. * We need to call unifi_bh() which in this implementation is done * by waking up the I/O thread. * * Arguments: * ospriv Pointer to OS driver structure for the device. * * Returns: * 0 on success or else a Linux error code. * * Notes: * --------------------------------------------------------------------------- */ CsrResult unifi_run_bh(void *ospriv) { unifi_priv_t *priv = ospriv; /* * If an error has occurred, we discard silently all messages from the bh * until the error has been processed and the unifi has been reinitialised. */ if (priv->bh_thread.block_thread == 1) { unifi_trace(priv, UDBG3, "unifi_run_bh: discard message.\n"); /* * Do not try to acknowledge a pending interrupt here. * This function is called by unifi_send_signal() which in turn can be * running in an atomic or 'disabled irq' level if a signal is sent * from a workqueue task (i.e multicass addresses set). * We can not hold the SDIO lock because it might sleep. */ return CSR_RESULT_FAILURE; } priv->bh_thread.wakeup_flag = 1; /* wake up I/O thread */ wake_up_interruptible(&priv->bh_thread.wakeup_q); return CSR_RESULT_SUCCESS; } /* unifi_run_bh() */