| /* |
| * --------------------------------------------------------------------------- |
| * 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() */ |
| |