diff options
Diffstat (limited to 'drivers/staging/csr/csr_wifi_hip_card_sdio_intr.c')
-rw-r--r-- | drivers/staging/csr/csr_wifi_hip_card_sdio_intr.c | 2595 |
1 files changed, 2595 insertions, 0 deletions
diff --git a/drivers/staging/csr/csr_wifi_hip_card_sdio_intr.c b/drivers/staging/csr/csr_wifi_hip_card_sdio_intr.c new file mode 100644 index 000000000000..97f645c06818 --- /dev/null +++ b/drivers/staging/csr/csr_wifi_hip_card_sdio_intr.c @@ -0,0 +1,2595 @@ +/***************************************************************************** + + (c) Cambridge Silicon Radio Limited 2012 + All rights reserved and confidential information of CSR + + Refer to LICENSE.txt included with this source for details + on the license terms. + +*****************************************************************************/ + +/* + * --------------------------------------------------------------------------- + * FILE: csr_wifi_hip_card_sdio_intr.c + * + * PURPOSE: + * Interrupt processing for the UniFi SDIO driver. + * + * We may need another signal queue of responses to UniFi to hold + * bulk data commands generated by read_to_host_signals(). + * + * --------------------------------------------------------------------------- + */ +#undef CSR_WIFI_HIP_NOISY + +#include "csr_wifi_hip_unifi.h" +#include "csr_wifi_hip_conversions.h" +#include "csr_wifi_hip_card.h" +#include "csr_wifi_hip_xbv.h" + + +/* + * If the SDIO link is idle for this time (in milliseconds), + * signal UniFi to go into Deep Sleep. + * Valid return value of unifi_bh(). + */ +#define UNIFI_DEFAULT_HOST_IDLE_TIMEOUT 5 +/* + * If the UniFi has not woken up for this time (in milliseconds), + * signal the bottom half to take action. + * Valid return value of unifi_bh(). + */ +#define UNIFI_DEFAULT_WAKE_TIMEOUT 1000 + + +static CsrResult process_bh(card_t *card); +static CsrResult handle_host_protocol(card_t *card, u8 *processed_something); + +static CsrResult flush_fh_buffer(card_t *card); + +static CsrResult check_fh_sig_slots(card_t *card, u16 needed, s32 *space); + +static CsrResult read_to_host_signals(card_t *card, s32 *processed); +static CsrResult process_to_host_signals(card_t *card, s32 *processed); + +static CsrResult process_bulk_data_command(card_t *card, + const u8 *cmdptr, + s16 cmd, u16 len); +static CsrResult process_clear_slot_command(card_t *card, + const u8 *cmdptr); +static CsrResult process_fh_cmd_queue(card_t *card, s32 *processed); +static CsrResult process_fh_traffic_queue(card_t *card, s32 *processed); +static void restart_packet_flow(card_t *card); +static CsrResult process_clock_request(card_t *card); + +#ifdef CSR_WIFI_HIP_NOISY +s16 dump_fh_buf = 0; +#endif /* CSR_WIFI_HIP_NOISY */ + +#ifdef CSR_WIFI_HIP_DEBUG_OFFLINE + +/* + * The unifi_debug_output buffer can be used to debug the HIP behaviour offline + * i.e. without using the tracing functions that change the timing. + * + * Call unifi_debug_log_to_buf() with printf arguments to store a string into + * unifi_debug_output. When unifi_debug_buf_dump() is called, the contents of the + * buffer are dumped with dump_str() which has to be implemented in the + * OS layer, during the porting exercise. The offset printed, holds the + * offset where the last character is (always a zero). + * + */ + +#define UNIFI_DEBUG_GBUFFER_SIZE 8192 +static char unifi_debug_output[UNIFI_DEBUG_GBUFFER_SIZE]; +static char *unifi_dbgbuf_ptr = unifi_debug_output; +static char *unifi_dbgbuf_start = unifi_debug_output; + +static void append_char(char c) +{ + /* write char and advance pointer */ + *unifi_dbgbuf_ptr++ = c; + /* wrap pointer at end of buffer */ + if ((unifi_dbgbuf_ptr - unifi_debug_output) >= UNIFI_DEBUG_GBUFFER_SIZE) + { + unifi_dbgbuf_ptr = unifi_debug_output; + } +} /* append_char() */ + + +void unifi_debug_string_to_buf(const char *str) +{ + const char *p = str; + while (*p) + { + append_char(*p); + p++; + } + /* Update start-of-buffer pointer */ + unifi_dbgbuf_start = unifi_dbgbuf_ptr + 1; + if ((unifi_dbgbuf_start - unifi_debug_output) >= UNIFI_DEBUG_GBUFFER_SIZE) + { + unifi_dbgbuf_start = unifi_debug_output; + } +} + + +void unifi_debug_log_to_buf(const char *fmt, ...) +{ +#define DEBUG_BUFFER_SIZE 80 + static char s[DEBUG_BUFFER_SIZE]; + va_list args; + + va_start(args, fmt); + vsnprintf(s, DEBUG_BUFFER_SIZE, fmt, args); + va_end(args); + + unifi_debug_string_to_buf(s); +} /* unifi_debug_log_to_buf() */ + + +/* Convert signed 32 bit (or less) integer to string */ +static void CsrUInt16ToHex(u16 number, char *str) +{ + u16 index; + u16 currentValue; + + for (index = 0; index < 4; index++) + { + currentValue = (u16) (number & 0x000F); + number >>= 4; + str[3 - index] = (char) (currentValue > 9 ? currentValue + 55 : currentValue + '0'); + } + str[4] = '\0'; +} + + +/* + * --------------------------------------------------------------------------- + * unifi_debug_hex_to_buf + * + * puts the contents of the passed buffer into the debug buffer as a hex string + * + * Arguments: + * buff buffer to print as hex + * length number of chars to print + * + * Returns: + * None. + * + * --------------------------------------------------------------------------- + */ +void unifi_debug_hex_to_buf(const char *buff, u16 length) +{ + char s[5]; + u16 i; + + for (i = 0; i < length; i = i + 2) + { + CsrUInt16ToHex(*((u16 *)(buff + i)), s); + unifi_debug_string_to_buf(s); + } +} + + +void unifi_debug_buf_dump(void) +{ + s32 offset = unifi_dbgbuf_ptr - unifi_debug_output; + + unifi_error(NULL, "HIP debug buffer offset=%d\n", offset); + dump_str(unifi_debug_output + offset, UNIFI_DEBUG_GBUFFER_SIZE - offset); + dump_str(unifi_debug_output, offset); +} /* unifi_debug_buf_dump() */ + + +#endif /* CSR_WIFI_HIP_DEBUG_OFFLINE */ + +#ifdef CSR_PRE_ALLOC_NET_DATA +#define NETDATA_PRE_ALLOC_BUF_SIZE 8000 + +void prealloc_netdata_free(card_t *card) +{ + unifi_warning(card->ospriv, "prealloc_netdata_free: IN: w=%d r=%d\n", card->prealloc_netdata_w, card->prealloc_netdata_r); + + while (card->bulk_data_desc_list[card->prealloc_netdata_r].data_length != 0) + { + unifi_warning(card->ospriv, "prealloc_netdata_free: r=%d\n", card->prealloc_netdata_r); + + unifi_net_data_free(card->ospriv, &card->bulk_data_desc_list[card->prealloc_netdata_r]); + card->prealloc_netdata_r++; + card->prealloc_netdata_r %= BULK_DATA_PRE_ALLOC_NUM; + } + card->prealloc_netdata_r = card->prealloc_netdata_w = 0; + + unifi_warning(card->ospriv, "prealloc_netdata_free: OUT: w=%d r=%d\n", card->prealloc_netdata_w, card->prealloc_netdata_r); +} + + +CsrResult prealloc_netdata_alloc(card_t *card) +{ + CsrResult r; + + unifi_trace(card->ospriv, UDBG5, "prealloc_netdata_alloc: IN: w=%d r=%d\n", card->prealloc_netdata_w, card->prealloc_netdata_r); + + while (card->bulk_data_desc_list[card->prealloc_netdata_w].data_length == 0) + { + r = unifi_net_data_malloc(card->ospriv, &card->bulk_data_desc_list[card->prealloc_netdata_w], NETDATA_PRE_ALLOC_BUF_SIZE); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "prealloc_netdata_alloc: Failed to allocate t-h bulk data\n"); + return CSR_RESULT_FAILURE; + } + card->prealloc_netdata_w++; + card->prealloc_netdata_w %= BULK_DATA_PRE_ALLOC_NUM; + } + unifi_trace(card->ospriv, UDBG5, "prealloc_netdata_alloc: OUT: w=%d r=%d\n", card->prealloc_netdata_w, card->prealloc_netdata_r); + + return CSR_RESULT_SUCCESS; +} + + +static CsrResult prealloc_netdata_get(card_t *card, bulk_data_desc_t *bulk_data_slot, u32 size) +{ + CsrResult r; + + unifi_trace(card->ospriv, UDBG5, "prealloc_netdata_get: IN: w=%d r=%d\n", card->prealloc_netdata_w, card->prealloc_netdata_r); + + if (card->bulk_data_desc_list[card->prealloc_netdata_r].data_length == 0) + { + unifi_error(card->ospriv, "prealloc_netdata_get: data_length = 0\n"); + } + + if ((size > NETDATA_PRE_ALLOC_BUF_SIZE) || (card->bulk_data_desc_list[card->prealloc_netdata_r].data_length == 0)) + { + unifi_warning(card->ospriv, "prealloc_netdata_get: Calling net_data_malloc\n"); + + r = unifi_net_data_malloc(card->ospriv, bulk_data_slot, size); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "prealloc_netdata_get: Failed to allocate t-h bulk data\n"); + return CSR_RESULT_FAILURE; + } + return CSR_RESULT_SUCCESS; + } + + *bulk_data_slot = card->bulk_data_desc_list[card->prealloc_netdata_r]; + card->bulk_data_desc_list[card->prealloc_netdata_r].os_data_ptr = NULL; + card->bulk_data_desc_list[card->prealloc_netdata_r].os_net_buf_ptr = NULL; + card->bulk_data_desc_list[card->prealloc_netdata_r].net_buf_length = 0; + card->bulk_data_desc_list[card->prealloc_netdata_r].data_length = 0; + + card->prealloc_netdata_r++; + card->prealloc_netdata_r %= BULK_DATA_PRE_ALLOC_NUM; + + unifi_trace(card->ospriv, UDBG5, "prealloc_netdata_get: OUT: w=%d r=%d\n", card->prealloc_netdata_w, card->prealloc_netdata_r); + + return CSR_RESULT_SUCCESS; +} + + +#endif + +/* + * --------------------------------------------------------------------------- + * unifi_sdio_interrupt_handler + * + * This function should be called by the OS-dependent code to handle + * an SDIO interrupt from the UniFi. + * + * Arguments: + * card Pointer to card context structure. + * + * Returns: + * None. + * + * Notes: This function may be called in DRS context. In this case, + * tracing with the unifi_trace(), etc, is not allowed. + * --------------------------------------------------------------------------- + */ +void unifi_sdio_interrupt_handler(card_t *card) +{ + /* + * Set the flag to say reason for waking was SDIO interrupt. + * Then ask the OS layer to run the unifi_bh to give attention to the UniFi. + */ + card->bh_reason_unifi = 1; + (void)unifi_run_bh(card->ospriv); +} /* sdio_interrupt_handler() */ + + +/* + * --------------------------------------------------------------------------- + * unifi_configure_low_power_mode + * + * This function should be called by the OS-dependent when + * the deep sleep signaling needs to be enabled or disabled. + * + * Arguments: + * card Pointer to card context structure. + * low_power_mode Disable/Enable the deep sleep signaling + * periodic_wake_mode UniFi wakes host periodically. + * + * Returns: + * CSR_RESULT_SUCCESS on success or a CSR error code. + * --------------------------------------------------------------------------- + */ +CsrResult unifi_configure_low_power_mode(card_t *card, + enum unifi_low_power_mode low_power_mode, + enum unifi_periodic_wake_mode periodic_wake_mode) +{ + card->low_power_mode = low_power_mode; + card->periodic_wake_mode = periodic_wake_mode; + + unifi_trace(card->ospriv, UDBG1, + "unifi_configure_low_power_mode: new mode = %s, wake_host = %s\n", + (low_power_mode == UNIFI_LOW_POWER_DISABLED)?"disabled" : "enabled", + (periodic_wake_mode == UNIFI_PERIODIC_WAKE_HOST_DISABLED)?"FALSE" : "TRUE"); + + (void)unifi_run_bh(card->ospriv); + return CSR_RESULT_SUCCESS; +} /* unifi_configure_low_power_mode() */ + + +/* + * --------------------------------------------------------------------------- + * unifi_force_low_power_mode + * + * This function should be called by the OS-dependent when + * UniFi needs to be set to the low power mode (e.g. on suspend) + * + * Arguments: + * card Pointer to card context structure. + * + * Returns: + * CSR_RESULT_SUCCESS on success or a CSR error code. + * --------------------------------------------------------------------------- + */ +CsrResult unifi_force_low_power_mode(card_t *card) +{ + if (card->low_power_mode == UNIFI_LOW_POWER_DISABLED) + { + unifi_error(card->ospriv, "Attempt to set mode to TORPID when lower power mode is disabled\n"); + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + return unifi_set_host_state(card, UNIFI_HOST_STATE_TORPID); +} /* unifi_force_low_power_mode() */ + + +/* + * --------------------------------------------------------------------------- + * unifi_bh + * + * This function should be called by the OS-dependent code when + * host and/or UniFi has requested an exchange of messages. + * + * Arguments: + * card Pointer to card context structure. + * + * Returns: + * CSR_RESULT_SUCCESS on success or a CSR error code. + * --------------------------------------------------------------------------- + */ +CsrResult unifi_bh(card_t *card, u32 *remaining) +{ + CsrResult r; + CsrResult csrResult; + u8 pending; + s32 iostate, j; + const enum unifi_low_power_mode low_power_mode = card->low_power_mode; + u16 data_slots_used = 0; + + + /* Process request to raise the maximum SDIO clock */ + r = process_clock_request(card); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Error setting maximum SDIO clock\n"); + goto exit; + } + + /* + * Why was the BH thread woken? + * If it was an SDIO interrupt, UniFi is awake and we need to process it. + * If it was a host process queueing data, then we need to awaken UniFi. + * + * Priority of flags is top down. + * + * ----------------------------------------------------------+ + * \state| AWAKE | DROWSY | TORPID | + * flag\ | | | | + * ---------+--------------+----------------+----------------| + * | do the host | go to AWAKE and| go to AWAKE and| + * unifi | protocol | do the host | do the host | + * | | protocol | protocol | + * ---------+--------------+----------------+----------------| + * | do the host | | | + * host | protocol | do nothing | go to DROWSY | + * | | | | + * ---------+--------------+----------------+----------------| + * | | | should not | + * timeout | go to TORPID | error, unifi | occur | + * | | didn't wake up | do nothing | + * ----------------------------------------------------------+ + * + * Note that if we end up in the AWAKE state we always do the host protocol. + */ + + do + { + /* + * When the host state is set to DROWSY, then we can not disable the + * interrupts as UniFi can generate an interrupt even when the INT_ENABLE + * register has the interrupts disabled. This interrupt will be lost. + */ + if (card->host_state == UNIFI_HOST_STATE_DROWSY || card->host_state == UNIFI_HOST_STATE_TORPID) + { + u8 reason_unifi; + + /* + * An interrupt may occur while or after we cache the reason. + * This interrupt will cause the unifi_bh() to be scheduled again. + * Any interrupt that has happened before the register is read + * and is considered spurious has to acknowledged. + */ + reason_unifi = card->bh_reason_unifi; + + /* + * If an interrupt is received, check if it was a real one, + * set the host state to AWAKE and run the BH. + */ + r = CardPendingInt(card, &pending); + if (r != CSR_RESULT_SUCCESS) + { + goto exit; + } + + if (pending) + { + unifi_trace(card->ospriv, UDBG5, + "UNIFI_HOST_STATE_%s: Set state to AWAKE.\n", + (card->host_state == UNIFI_HOST_STATE_TORPID)?"TORPID" : "DROWSY"); + + r = unifi_set_host_state(card, UNIFI_HOST_STATE_AWAKE); + if (r == CSR_RESULT_SUCCESS) + { + (*remaining) = 0; + break; + } + } + else if (reason_unifi) + { + CsrSdioInterruptAcknowledge(card->sdio_if); + } + + /* + * If an chip is in TORPID, and the host wants to wake it up, + * set the host state to DROWSY and wait for the wake-up interrupt. + */ + if ((card->host_state == UNIFI_HOST_STATE_TORPID) && card->bh_reason_host) + { + r = unifi_set_host_state(card, UNIFI_HOST_STATE_DROWSY); + if (r == CSR_RESULT_SUCCESS) + { + /* + * set the timeout value to UNIFI_DEFAULT_WAKE_TIMEOUT + * to capture a wake error. + */ + card->bh_reason_host = 0; + (*remaining) = UNIFI_DEFAULT_WAKE_TIMEOUT; + return CSR_RESULT_SUCCESS; + } + + goto exit; + } + + /* + * If the chip is in DROWSY, and the timeout expires, + * we need to reset the chip. This should never occur. + * (If it does, check that the calling thread set "remaining" + * according to the time remaining when unifi_bh() was called). + */ + if ((card->host_state == UNIFI_HOST_STATE_DROWSY) && ((*remaining) == 0)) + { + unifi_error(card->ospriv, "UniFi did not wake up on time...\n"); + + /* + * Check if Function1 has gone away or + * if we missed an SDIO interrupt. + */ + r = unifi_check_io_status(card, &iostate); + if (r == CSR_WIFI_HIP_RESULT_NO_DEVICE) + { + goto exit; + } + /* Need to reset and reboot */ + return CSR_RESULT_FAILURE; + } + } + else + { + if (card->bh_reason_unifi || card->bh_reason_host) + { + break; + } + + if (((*remaining) == 0) && (low_power_mode == UNIFI_LOW_POWER_ENABLED)) + { + r = unifi_set_host_state(card, UNIFI_HOST_STATE_TORPID); + if (r == CSR_RESULT_SUCCESS) + { + (*remaining) = 0; + return CSR_RESULT_SUCCESS; + } + + goto exit; + } + } + + /* No need to run the host protocol */ + return CSR_RESULT_SUCCESS; + } while (0); + + + /* Disable the SDIO interrupts while doing SDIO ops */ + csrResult = CsrSdioInterruptDisable(card->sdio_if); + if (csrResult == CSR_SDIO_RESULT_NO_DEVICE) + { + r = CSR_WIFI_HIP_RESULT_NO_DEVICE; + goto exit; + } + if (csrResult != CSR_RESULT_SUCCESS) + { + r = ConvertCsrSdioToCsrHipResult(card, csrResult); + unifi_error(card->ospriv, "Failed to disable SDIO interrupts. unifi_bh queues error.\n"); + goto exit; + } + + /* Now that the interrupts are disabled, ack the interrupt */ + CsrSdioInterruptAcknowledge(card->sdio_if); + + /* Run the HIP */ + r = process_bh(card); + if (r != CSR_RESULT_SUCCESS) + { + goto exit; + } + + /* + * If host is now idle, schedule a timer for the delay before we + * let UniFi go into deep sleep. + * If the timer goes off, we will move to TORPID state. + * If UniFi raises an interrupt in the meantime, we will cancel + * the timer and start a new one when we become idle. + */ + for (j = 0; j < UNIFI_NO_OF_TX_QS; j++) + { + data_slots_used += CSR_WIFI_HIP_Q_SLOTS_USED(&card->fh_traffic_queue[j]); + } + + if ((low_power_mode == UNIFI_LOW_POWER_ENABLED) && (data_slots_used == 0)) + { +#ifndef CSR_WIFI_HIP_TA_DISABLE + if (card->ta_sampling.traffic_type != CSR_WIFI_ROUTER_CTRL_TRAFFIC_TYPE_PERIODIC) + { +#endif + /* return the UNIFI_DEFAULT_HOST_IDLE_TIMEOUT, so we can go to sleep. */ + unifi_trace(card->ospriv, UDBG5, + "Traffic is not periodic, set timer for TORPID.\n"); + (*remaining) = UNIFI_DEFAULT_HOST_IDLE_TIMEOUT; +#ifndef CSR_WIFI_HIP_TA_DISABLE + } + else + { + unifi_trace(card->ospriv, UDBG5, + "Traffic is periodic, set unifi to TORPID immediately.\n"); + if (CardAreAllFromHostDataSlotsEmpty(card) == 1) + { + r = unifi_set_host_state(card, UNIFI_HOST_STATE_TORPID); + if (r != CSR_RESULT_SUCCESS) + { + goto exit; + } + } + } +#endif + } + + csrResult = CsrSdioInterruptEnable(card->sdio_if); + if (csrResult == CSR_SDIO_RESULT_NO_DEVICE) + { + r = CSR_WIFI_HIP_RESULT_NO_DEVICE; + } + if (csrResult != CSR_RESULT_SUCCESS) + { + r = ConvertCsrSdioToCsrHipResult(card, csrResult); + unifi_error(card->ospriv, "Failed to enable SDIO interrupt\n"); + } + +exit: + + unifi_trace(card->ospriv, UDBG4, "New state=%d\n", card->host_state); + + if (r != CSR_RESULT_SUCCESS) + { +#if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) && defined (CSR_WIFI_HIP_SDIO_TRACE) + unifi_debug_buf_dump(); +#endif + /* If an interrupt has been raised, ack it here */ + if (card->bh_reason_unifi) + { + CsrSdioInterruptAcknowledge(card->sdio_if); + } + + unifi_error(card->ospriv, + "unifi_bh: state=%d %c, clock=%dkHz, interrupt=%d host=%d, power_save=%s\n", + card->host_state, + (card->host_state == UNIFI_HOST_STATE_AWAKE)?'A' : (card->host_state == UNIFI_HOST_STATE_DROWSY)?'D' : 'T', + card->sdio_clock_speed / 1000, + card->bh_reason_unifi, card->bh_reason_host, + (low_power_mode == UNIFI_LOW_POWER_DISABLED)?"disabled" : "enabled"); + + /* Try to capture firmware panic codes */ + (void)unifi_capture_panic(card); + + /* Ask for a mini-coredump when the driver has reset UniFi */ + (void)unifi_coredump_request_at_next_reset(card, 1); + } + + return r; +} /* unifi_bh() */ + + +/* + * --------------------------------------------------------------------------- + * process_clock_request + * + * Handle request from the OS layer to increase the SDIO clock speed. + * The fast clock is limited until the firmware has indicated that it has + * completed initialisation to the OS layer. + * + * Arguments: + * card Pointer to card context structure. + * + * Returns: + * CSR_RESULT_SUCCESS on success or CSR error code. + * --------------------------------------------------------------------------- + */ +static CsrResult process_clock_request(card_t *card) +{ + CsrResult r = CSR_RESULT_SUCCESS; + CsrResult csrResult; + + if (!card->request_max_clock) + { + return CSR_RESULT_SUCCESS; /* No pending request */ + } + + /* + * The SDIO clock speed request from the OS layer is only acted upon if + * the UniFi is awake. If it was in any other state, the clock speed will + * transition through SAFE to MAX while the host wakes it up, and the + * final speed reached will be UNIFI_SDIO_CLOCK_MAX_HZ. + * This assumes that the SME never requests low power mode while the f/w + * initialisation takes place. + */ + if (card->host_state == UNIFI_HOST_STATE_AWAKE) + { + unifi_trace(card->ospriv, UDBG1, "Set SDIO max clock\n"); + csrResult = CsrSdioMaxBusClockFrequencySet(card->sdio_if, UNIFI_SDIO_CLOCK_MAX_HZ); + if (csrResult != CSR_RESULT_SUCCESS) + { + r = ConvertCsrSdioToCsrHipResult(card, csrResult); + } + else + { + card->sdio_clock_speed = UNIFI_SDIO_CLOCK_MAX_HZ; /* log the new freq */ + } + } + else + { + unifi_trace(card->ospriv, UDBG1, "Will set SDIO max clock after wakeup\n"); + } + + /* Cancel the request now that it has been acted upon, or is about to be + * by the wakeup mechanism + */ + card->request_max_clock = 0; + + return r; +} + + +/* + * --------------------------------------------------------------------------- + * process_bh + * + * Exchange messages with UniFi + * + * Arguments: + * card Pointer to card context structure. + * + * Returns: + * CSR_RESULT_SUCCESS on success or CSR error code. + * --------------------------------------------------------------------------- + */ +static CsrResult process_bh(card_t *card) +{ + CsrResult r; + u8 more; + more = FALSE; + + /* Process the reasons (interrupt, signals) */ + do + { + /* + * Run in a while loop, to save clearing the interrupts + * every time around the outside loop. + */ + do + { + /* If configured to run the HIP just once, skip first loop */ + if (card->intmode & CSR_WIFI_INTMODE_RUN_BH_ONCE) + { + break; + } + + r = handle_host_protocol(card, &more); + if (r != CSR_RESULT_SUCCESS) + { + return r; + } + +#if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) && defined (CSR_WIFI_HIP_DATA_PLANE_PROFILE) + unifi_debug_log_to_buf("c52=%d c53=%d tx=%d txc=%d rx=%d s=%d t=%d fc=%d\n", + card->cmd_prof.cmd52_count, + card->cmd_prof.cmd53_count, + card->cmd_prof.tx_count, + card->cmd_prof.tx_cfm_count, + card->cmd_prof.rx_count, + card->cmd_prof.sdio_cmd_signal, + card->cmd_prof.sdio_cmd_to_host, + card->cmd_prof.sdio_cmd_from_host_and_clear + ); + + card->cmd_prof.cmd52_count = card->cmd_prof.cmd53_count = 0; + card->cmd_prof.tx_count = card->cmd_prof.tx_cfm_count = card->cmd_prof.rx_count = 0; + + card->cmd_prof.cmd52_f0_r_count = 0; + card->cmd_prof.cmd52_f0_w_count = 0; + card->cmd_prof.cmd52_r8or16_count = 0; + card->cmd_prof.cmd52_w8or16_count = 0; + card->cmd_prof.cmd52_r16_count = 0; + card->cmd_prof.cmd52_w16_count = 0; + card->cmd_prof.cmd52_r32_count = 0; + + card->cmd_prof.sdio_cmd_signal = 0; + card->cmd_prof.sdio_cmd_clear_slot = 0; + card->cmd_prof.sdio_cmd_to_host = 0; + card->cmd_prof.sdio_cmd_from_host = 0; + card->cmd_prof.sdio_cmd_from_host_and_clear = 0; +#endif + + + } while (more || card->bh_reason_unifi || card->bh_reason_host); + + /* Acknowledge the h/w interrupt */ + r = CardClearInt(card); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to acknowledge interrupt.\n"); + return r; + } + + /* + * UniFi may have tried to generate an interrupt during the + * CardClearInt() was running. So, we need to run the host + * protocol again, to check if there are any pending requests. + */ + r = handle_host_protocol(card, &more); + if (r != CSR_RESULT_SUCCESS) + { + return r; + } + +#if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) && defined (CSR_WIFI_HIP_DATA_PLANE_PROFILE) + unifi_debug_log_to_buf("c52=%d c53=%d tx=%d txc=%d rx=%d s=%d t=%d fc=%d\n", + card->cmd_prof.cmd52_count, + card->cmd_prof.cmd53_count, + card->cmd_prof.tx_count, + card->cmd_prof.tx_cfm_count, + card->cmd_prof.rx_count, + card->cmd_prof.sdio_cmd_signal, + card->cmd_prof.sdio_cmd_to_host, + card->cmd_prof.sdio_cmd_from_host_and_clear + ); + + card->cmd_prof.cmd52_count = card->cmd_prof.cmd53_count = 0; + card->cmd_prof.tx_count = card->cmd_prof.tx_cfm_count = card->cmd_prof.rx_count = 0; + + card->cmd_prof.cmd52_f0_r_count = 0; + card->cmd_prof.cmd52_f0_w_count = 0; + card->cmd_prof.cmd52_r8or16_count = 0; + card->cmd_prof.cmd52_w8or16_count = 0; + card->cmd_prof.cmd52_r16_count = 0; + card->cmd_prof.cmd52_w16_count = 0; + card->cmd_prof.cmd52_r32_count = 0; + + card->cmd_prof.sdio_cmd_signal = 0; + card->cmd_prof.sdio_cmd_clear_slot = 0; + card->cmd_prof.sdio_cmd_to_host = 0; + card->cmd_prof.sdio_cmd_from_host = 0; + card->cmd_prof.sdio_cmd_from_host_and_clear = 0; +#endif + /* If configured to run the HIP just once, work is now done */ + if (card->intmode & CSR_WIFI_INTMODE_RUN_BH_ONCE) + { + break; + } + + } while (more || card->bh_reason_unifi || card->bh_reason_host); + +#if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) && defined (CSR_WIFI_HIP_DATA_PLANE_PROFILE) + if ((card->intmode & CSR_WIFI_INTMODE_RUN_BH_ONCE) == 0) + { + unifi_debug_log_to_buf("proc=%d\n", + card->cmd_prof.process_count); + } +#endif + + return CSR_RESULT_SUCCESS; +} /* process_bh() */ + + +/* + * --------------------------------------------------------------------------- + * handle_host_protocol + * + * This function implements the Host Interface Protocol (HIP) as + * described in the Host Interface Protocol Specification. + * + * Arguments: + * card Pointer to card context structure. + * processed_something Pointer to location to update processing status: + * TRUE when data was transferred + * FALSE when no data was transferred (queues empty) + * + * Returns: + * CSR_RESULT_SUCCESS on success or CSR error code. + * --------------------------------------------------------------------------- + */ +static CsrResult handle_host_protocol(card_t *card, u8 *processed_something) +{ + CsrResult r; + s32 done; + + *processed_something = FALSE; + +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, " ======================== \n"); +#endif /* CSR_WIFI_HIP_NOISY */ + +#ifdef CSR_WIFI_HIP_DATA_PLANE_PROFILE + card->cmd_prof.process_count++; +#endif + + card->bh_reason_unifi = card->bh_reason_host = 0; + card->generate_interrupt = 0; + + + /* + * (Re)fill the T-H signal buffer + */ + r = read_to_host_signals(card, &done); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Error occured reading to-host signals\n"); + return r; + } + if (done > 0) + { + *processed_something = TRUE; + } + + /* + * Process any to-host signals. + * Perform any requested CMD53 transfers here, but just queue any + * bulk data command responses. + */ + r = process_to_host_signals(card, &done); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Error occured processing to-host signals\n"); + return r; + } + + /* Now send any signals in the F-H queues */ + /* Give precedence to the command queue */ + r = process_fh_cmd_queue(card, &done); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Error occured processing from-host signals\n"); + return r; + } + if (done > 0) + { + *processed_something = TRUE; + } + + r = process_fh_traffic_queue(card, &done); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Error occured processing from-host data signals\n"); + return r; + } + if (done > 0) + { + *processed_something = TRUE; + } + + /* Flush out the batch of signals to the UniFi. */ + r = flush_fh_buffer(card); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to copy from-host signals to UniFi\n"); + return r; + } + + + /* + * Send the host interrupt to say the queues have been modified. + */ + if (card->generate_interrupt) + { + r = CardGenInt(card); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to notify UniFi that queues have been modified.\n"); + return r; + } + } + +#ifdef CSR_WIFI_RX_PATH_SPLIT +#ifdef CSR_WIFI_RX_PATH_SPLIT_DONT_USE_WQ + unifi_rx_queue_flush(card->ospriv); +#endif +#endif + + /* See if we can re-enable transmission now */ + restart_packet_flow(card); + +#ifdef CSR_PRE_ALLOC_NET_DATA + r = prealloc_netdata_alloc(card); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "prealloc_netdata failed\n"); + return r; + } +#endif + + /* + * Don't put the thread sleep if we just interacted with the chip, + * there might be more to do if we look again. + */ + return r; +} /* handle_host_protocol() */ + + +/* + * Rounds the given signal length in bytes to a whole number + * of sig_frag_size. + */ +#define GET_CHUNKS_FOR(SIG_FRAG_SIZE, LENGTH) (((LENGTH) + ((SIG_FRAG_SIZE)-1)) / (SIG_FRAG_SIZE)) + + +/* + * --------------------------------------------------------------------------- + * read_to_host_signals + * + * Read everything pending in the UniFi TH signal buffer. + * Only do it if the local buffer is empty. + * + * Arguments: + * card Pointer to card context struct + * processed Number of signals read: + * 0 if there were no signals pending, + * 1 if we read at least one signal + * Returns: + * CSR error code if an error occurred. + * --------------------------------------------------------------------------- + */ +static CsrResult read_to_host_signals(card_t *card, s32 *processed) +{ + s32 count_thw, count_thr; + s32 unread_chunks, unread_bytes; + CsrResult r; + + *processed = 0; + + /* Read any pending signals or bulk data commands */ + count_thw = unifi_read_shared_count(card, card->sdio_ctrl_addr + 4); + if (count_thw < 0) + { + unifi_error(card->ospriv, "Failed to read to-host sig written count\n"); + return CSR_RESULT_FAILURE; + } + card->to_host_signals_w = count_thw; /* diag */ + + count_thr = card->to_host_signals_r; + + if (count_thw == count_thr) + { + return CSR_RESULT_SUCCESS; + } + + unread_chunks = + (((count_thw - count_thr) + 128) % 128) - card->th_buffer.count; + + if (unread_chunks == 0) + { + return CSR_RESULT_SUCCESS; + } + + unread_bytes = card->config_data.sig_frag_size * unread_chunks; + + + r = unifi_bulk_rw(card, + card->config_data.tohost_sigbuf_handle, + card->th_buffer.ptr, + unread_bytes, + UNIFI_SDIO_READ); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to read ToHost signal\n"); + return r; + } + + card->th_buffer.ptr += unread_bytes; + card->th_buffer.count += (u16)unread_chunks; + + *processed = 1; + + return CSR_RESULT_SUCCESS; +} /* read_to_host_signals() */ + + +/* + * --------------------------------------------------------------------------- + * update_to_host_signals_r + * + * Advance the shared-memory count of chunks read from the to-host + * signal buffer. + * Raise a UniFi internal interrupt to tell the firmware that the + * count has changed. + * + * Arguments: + * card Pointer to card context struct + * pending Number of chunks remaining + * + * Returns: + * CSR_RESULT_SUCCESS on success or CSR error code + * --------------------------------------------------------------------------- + */ +static CsrResult update_to_host_signals_r(card_t *card, s16 pending) +{ + CsrResult r; + + card->to_host_signals_r = + (card->to_host_signals_r + (card->th_buffer.count - pending)) % 128; + card->th_buffer.count = pending; + + /* Update the count of signals read */ + r = unifi_write_8_or_16(card, card->sdio_ctrl_addr + 6, + (u8)card->to_host_signals_r); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to update to-host signals read\n"); + return r; + } + + r = CardGenInt(card); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to notify UniFi that we processed to-host signals.\n"); + return r; + } + + card->generate_interrupt = 0; + + return CSR_RESULT_SUCCESS; +} /* update_to_host_signals_r() */ + + +/* + * --------------------------------------------------------------------------- + * read_unpack_cmd + * + * Converts a wire-formatted command to the host bulk_data_cmd_t structure. + * + * Arguments: + * ptr Pointer to the command + * bulk_data_cmd Pointer to the host structure + * + * Returns: + * None. + * --------------------------------------------------------------------------- + */ +static void read_unpack_cmd(const u8 *ptr, bulk_data_cmd_t *bulk_data_cmd) +{ + s16 index = 0; + bulk_data_cmd->cmd_and_len = CSR_GET_UINT16_FROM_LITTLE_ENDIAN(ptr + index); + index += SIZEOF_UINT16; + bulk_data_cmd->data_slot = CSR_GET_UINT16_FROM_LITTLE_ENDIAN(ptr + index); + index += SIZEOF_UINT16; + bulk_data_cmd->offset = CSR_GET_UINT16_FROM_LITTLE_ENDIAN(ptr + index); + index += SIZEOF_UINT16; + bulk_data_cmd->buffer_handle = CSR_GET_UINT16_FROM_LITTLE_ENDIAN(ptr + index); + index += SIZEOF_UINT16; +} /* read_unpack_cmd */ + + +/* + * --------------------------------------------------------------------------- + * process_to_host_signals + * + * Read and dispatch signals from the UniFi + * + * Arguments: + * card Pointer to card context struct + * processed Pointer to location to write processing result: + * 0 if there were no signals pending, + * 1 if we read at least one signal + * + * Returns: + * CSR error code if there was an error + * + * Notes: + * Since bulk data transfers can take a long time, if we wait until + * all are done before we acknowledge the signals, the UniFi runs out + * of buffer space. Therefore we keep a count of the bytes transferred + * in bulk data commands, and update the to-host-signals-read count + * if we've done a large transfer. + * + * All data in the f/w is stored in a little endian format, without any + * padding bytes. Every read from the memory has to be transformed in + * host (cpu specific) format, before we can process it. Therefore we + * use read_unpack_cmd() and read_unpack_signal() to convert the raw data + * contained in the card->th_buffer.buf to host structures. + * Important: UDI clients use wire-formatted structures, so we need to + * indicate all data, as we have read it from the device. + * --------------------------------------------------------------------------- + */ +static CsrResult process_to_host_signals(card_t *card, s32 *processed) +{ + s16 pending; + s16 remaining; + u8 *bufptr; + bulk_data_param_t data_ptrs; + s16 cmd; + u16 sig_len; + s16 i; + u16 chunks_in_buf; + u16 bytes_transferred = 0; + CsrResult r = CSR_RESULT_SUCCESS; + + *processed = 0; + + pending = card->th_buffer.count; + + /* Are there new to-host signals? */ + unifi_trace(card->ospriv, UDBG4, "handling %d to-host chunks\n", pending); + + if (!pending) + { + return CSR_RESULT_SUCCESS; + } + + /* + * This is a pointer to the raw data we have read from the f/w. + * Can be a signal or a command. Note that we need to convert + * it to a host structure before we process it. + */ + bufptr = card->th_buffer.buf; + + while (pending > 0) + { + s16 f_flush_count = 0; + + /* + * Command and length are common to signal and bulk data msgs. + * If command == 0 (i.e. a signal), len is number of bytes + * *following* the 2-byte header. + */ + cmd = bufptr[1] >> 4; + sig_len = bufptr[0] + ((bufptr[1] & 0x0F) << 8); + +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "Received UniFi msg cmd=%d, len=%d\n", + cmd, sig_len); +#endif /* CSR_WIFI_HIP_NOISY */ + + if ((sig_len == 0) && + ((cmd != SDIO_CMD_CLEAR_SLOT) && (cmd != SDIO_CMD_PADDING))) + { + unifi_error(card->ospriv, "incomplete signal or command: has size zero\n"); + return CSR_RESULT_FAILURE; + } + /* + * Make sure the buffer contains a complete message. + * Signals may occupy multiple chunks, bulk-data commands occupy + * one chunk. + */ + if (cmd == SDIO_CMD_SIGNAL) + { + chunks_in_buf = GET_CHUNKS_FOR(card->config_data.sig_frag_size, (u16)(sig_len + 2)); + } + else + { + chunks_in_buf = 1; + } + + if (chunks_in_buf > (u16)pending) + { + unifi_error(card->ospriv, "incomplete signal (0x%x?): need %d chunks, got %d\n", + GET_SIGNAL_ID(bufptr + 2), + chunks_in_buf, pending); + unifi_error(card->ospriv, " thsw=%d, thsr=%d\n", + card->to_host_signals_w, + card->to_host_signals_r); + return CSR_RESULT_FAILURE; + } + + + switch (cmd) + { + case SDIO_CMD_SIGNAL: + /* This is a signal. Read the rest of it and then handle it. */ +#if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) && defined (CSR_WIFI_HIP_DATA_PLANE_PROFILE) + card->cmd_prof.sdio_cmd_signal++; +#endif + + for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; i++) + { + /* Retrieve dataRefs[i].DataLength */ + u16 data_len = GET_PACKED_DATAREF_LEN(bufptr + 2, i); + + /* + * The bulk data length in the signal can not be greater than + * the maximun length allowed by the SDIO config structure. + */ + if (data_len > card->config_data.data_slot_size) + { + unifi_error(card->ospriv, + "Bulk Data length (%d) exceeds Maximum Bulk Data length (%d)\n", + data_len, card->config_data.data_slot_size); + return CSR_RESULT_FAILURE; + } + + /* + * Len here might not be the same as the length in the + * bulk data slot. The slot length will always be even, + * but len could be odd. + */ + if (data_len != 0) + { + /* Retrieve dataRefs[i].SlotNumber */ + s16 slot = GET_PACKED_DATAREF_SLOT(bufptr + 2, i); + + if (slot >= card->config_data.num_tohost_data_slots) + { + unifi_error(card->ospriv, "!!!bad slot number in to-host signal: %d, sig 0x%X\n", + slot, cmd); + return CSR_RESULT_FAILURE; + } + + data_ptrs.d[i].os_data_ptr = card->to_host_data[slot].os_data_ptr; + data_ptrs.d[i].os_net_buf_ptr = card->to_host_data[slot].os_net_buf_ptr; + data_ptrs.d[i].net_buf_length = card->to_host_data[slot].net_buf_length; + data_ptrs.d[i].data_length = data_len; + } + else + { + UNIFI_INIT_BULK_DATA(&data_ptrs.d[i]); + } + } + + /* + * Log the signal to the UDI, before call unifi_receive_event() as + * it can modify the bulk data. + */ + if (card->udi_hook) + { + (*card->udi_hook)(card->ospriv, bufptr + 2, sig_len, + &data_ptrs, UDI_LOG_TO_HOST); + } + +#ifdef CSR_WIFI_HIP_DATA_PLANE_PROFILE + if (GET_SIGNAL_ID(bufptr + 2) == CSR_MA_PACKET_CONFIRM_ID) + { + card->cmd_prof.tx_cfm_count++; + } + else if (GET_SIGNAL_ID(bufptr + 2) == CSR_MA_PACKET_INDICATION_ID) + { + if (data_ptrs.d[0].os_data_ptr) + { + if ((*data_ptrs.d[0].os_data_ptr) & 0x08) + { + card->cmd_prof.rx_count++; + } + } + } +#endif + /* + * Check if the signal is MA-PACKET.cfm and if so check the status. + * If the status is failure, search through the slot records to find + * if any slots are occupied for this host tag. This can happen if + * f/w has not downloaded the bulkdata and before that itself it has + * signalled the confirm with failure. If it finds a slot with that + * host tag then, it clears the corresponding slot + */ + + if (GET_SIGNAL_ID(bufptr + 2) == CSR_MA_PACKET_CONFIRM_ID) + { + /* Get host tag and transmission status */ + u32 host_tag = GET_PACKED_MA_PACKET_CONFIRM_HOST_TAG(bufptr + 2); + u16 status = GET_PACKED_MA_PACKET_CONFIRM_TRANSMISSION_STATUS(bufptr + 2); + + unifi_trace(card->ospriv, UDBG4, "process_to_host_signals signal ID=%x host Tag=%x status=%x\n", + GET_SIGNAL_ID(bufptr + 2), host_tag, status); + + /* If transmission status is failure then search through the slot records + * and if for any slot records the clear slot is not done then do it now + */ + + if (status && (card->fh_slot_host_tag_record)) + { + u16 num_fh_slots = card->config_data.num_fromhost_data_slots; + + /* search through the list of slot records and match with host tag + * If a slot is not yet cleared then clear the slot from here + */ + for (i = 0; i < num_fh_slots; i++) + { + if (card->fh_slot_host_tag_record[i] == host_tag) + { +#ifdef CSR_WIFI_REQUEUE_PACKET_TO_HAL + /* Invoke the HAL module function to requeue it back to HAL Queues */ + r = unifi_reque_ma_packet_request(card->ospriv, host_tag, status, &card->from_host_data[i].bd); + card->fh_slot_host_tag_record[i] = CSR_WIFI_HIP_RESERVED_HOST_TAG; + if (CSR_RESULT_SUCCESS != r) + { + unifi_trace(card->ospriv, UDBG5, "process_to_host_signals: Failed to requeue Packet(hTag:%x) back to HAL \n", host_tag); + CardClearFromHostDataSlot(card, i); + } + else + { + CardClearFromHostDataSlotWithoutFreeingBulkData(card, i); + } + +#else + unifi_trace(card->ospriv, UDBG4, "process_to_host_signals Clear slot=%x host tag=%x\n", i, host_tag); + card->fh_slot_host_tag_record[i] = CSR_WIFI_HIP_RESERVED_HOST_TAG; + + /* Set length field in from_host_data array to 0 */ + CardClearFromHostDataSlot(card, i); +#endif + break; + } + } + } + } + + /* Pass event to OS layer */ + unifi_receive_event(card->ospriv, bufptr + 2, sig_len, &data_ptrs); + + /* Initialise the to_host data, so it can be re-used. */ + for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; i++) + { + /* The slot is only valid if the length is non-zero. */ + if (GET_PACKED_DATAREF_LEN(bufptr + 2, i) != 0) + { + s16 slot = GET_PACKED_DATAREF_SLOT(bufptr + 2, i); + if (slot < card->config_data.num_tohost_data_slots) + { + UNIFI_INIT_BULK_DATA(&card->to_host_data[slot]); + } + } + } + +#ifndef CSR_WIFI_DEFER_TH_FLUSH + /* + * If we have previously transferred a lot of data, ack + * the signals read so far, so f/w can reclaim the buffer + * memory sooner. + */ + if (bytes_transferred >= TO_HOST_FLUSH_THRESHOLD) + { + f_flush_count = 1; + } +#endif + break; + + + case SDIO_CMD_CLEAR_SLOT: +#if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) && defined (CSR_WIFI_HIP_DATA_PLANE_PROFILE) + card->cmd_prof.sdio_cmd_clear_slot++; +#endif + /* This is a clear slot command. */ + if (sig_len != 0) + { + unifi_error(card->ospriv, "process_to_host_signals: clear slot, bad data len: 0x%X at offset %d\n", + sig_len, bufptr - card->th_buffer.buf); + return CSR_RESULT_FAILURE; + } + + r = process_clear_slot_command(card, bufptr); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to process clear slot\n"); + return r; + } + break; + + case SDIO_CMD_TO_HOST_TRANSFER: + case SDIO_CMD_FROM_HOST_TRANSFER: + case SDIO_CMD_FROM_HOST_AND_CLEAR: + case SDIO_CMD_OVERLAY_TRANSFER: + /* This is a bulk data command. */ + if (sig_len & 1) + { + unifi_error(card->ospriv, "process_to_host_signals: bulk data, bad data len: 0x%X at offset %d\n", + sig_len, bufptr - card->th_buffer.buf); + return CSR_RESULT_FAILURE; + } + + r = process_bulk_data_command(card, bufptr, cmd, sig_len); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to process bulk cmd\n"); + return r; + } + /* Count the bytes transferred */ + bytes_transferred += sig_len; + + if (cmd == SDIO_CMD_FROM_HOST_AND_CLEAR) + { +#if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) && defined (CSR_WIFI_HIP_DATA_PLANE_PROFILE) + card->cmd_prof.sdio_cmd_from_host_and_clear++; +#endif +#ifndef CSR_WIFI_DEFER_TH_FLUSH + f_flush_count = 1; +#endif + } +#if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) && defined (CSR_WIFI_HIP_DATA_PLANE_PROFILE) + else if (cmd == SDIO_CMD_FROM_HOST_TRANSFER) + { + card->cmd_prof.sdio_cmd_from_host++; + } + else if (cmd == SDIO_CMD_TO_HOST_TRANSFER) + { + card->cmd_prof.sdio_cmd_to_host++; + } +#endif + break; + + case SDIO_CMD_PADDING: + break; + + default: + unifi_error(card->ospriv, "Unrecognised to-host command: %d\n", cmd); + break; + } + + bufptr += chunks_in_buf * card->config_data.sig_frag_size; + pending -= chunks_in_buf; + + /* + * Write out the host signal count when a significant + * number of bytes of bulk data have been transferred or + * when we have performed a CopyFromHostAndClear. + */ + if (f_flush_count) + { + r = update_to_host_signals_r(card, pending); + if (r != CSR_RESULT_SUCCESS) + { + return r; + } + bytes_transferred = 0; + } + } + + if (pending) + { + unifi_warning(card->ospriv, "proc_th_sigs: %d unprocessed\n", pending); + } + + /* If we processed any signals, write the updated count to UniFi */ + if (card->th_buffer.count != pending) + { + r = update_to_host_signals_r(card, pending); + if (r != CSR_RESULT_SUCCESS) + { + return r; + } + } + + /* + * Reset the buffer pointer, copying down any un-processed signals. + * This can happen if we enable the optimisation in read_to_host_signals() + * that limits the length to whole blocks. + */ + remaining = card->th_buffer.ptr - bufptr; + if (remaining < 0) + { + unifi_error(card->ospriv, "Processing TH signals overran the buffer\n"); + return CSR_RESULT_FAILURE; + } + if (remaining > 0) + { + /* Use a safe copy because source and destination may overlap */ + u8 *d = card->th_buffer.buf; + u8 *s = bufptr; + s32 n = remaining; + while (n--) + { + *d++ = *s++; + } + } + card->th_buffer.ptr = card->th_buffer.buf + remaining; + + + /* If we reach here then we processed something */ + *processed = 1; + return CSR_RESULT_SUCCESS; +} /* process_to_host_signals() */ + + +/* + * --------------------------------------------------------------------------- + * process_clear_slot_command + * + * Process a clear slot command fom the UniFi. + * + * Arguments: + * card Pointer to card context struct + * bdcmd Pointer to bulk-data command msg from UniFi + * + * Returns: + * 0 on success, CSR error code on error + * --------------------------------------------------------------------------- + */ +static CsrResult process_clear_slot_command(card_t *card, const u8 *cmdptr) +{ + u16 data_slot; + s16 slot; + + data_slot = CSR_GET_UINT16_FROM_LITTLE_ENDIAN(cmdptr + SIZEOF_UINT16); + + unifi_trace(card->ospriv, UDBG4, "Processing clear slot cmd, slot=0x%X\n", + data_slot); + + slot = data_slot & 0x7FFF; + +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "CMD clear data slot 0x%04x\n", data_slot); +#endif /* CSR_WIFI_HIP_NOISY */ + + if (data_slot & SLOT_DIR_TO_HOST) + { + if (slot >= card->config_data.num_tohost_data_slots) + { + unifi_error(card->ospriv, + "Invalid to-host data slot in SDIO_CMD_CLEAR_SLOT: %d\n", + slot); + return CSR_RESULT_FAILURE; + } + /* clear to-host data slot */ + unifi_warning(card->ospriv, "Unexpected clear to-host data slot cmd: 0x%04x\n", + data_slot); + } + else + { + if (slot >= card->config_data.num_fromhost_data_slots) + { + unifi_error(card->ospriv, + "Invalid from-host data slot in SDIO_CMD_CLEAR_SLOT: %d\n", + slot); + return CSR_RESULT_FAILURE; + } + + /* + * The driver is the owner to clear all slots now + * Ref - comment in process_fh_traffic_queue + * so it will just ignore the clear slot command from firmware + * and return success + */ + return CSR_RESULT_SUCCESS; + + /* Set length field in from_host_data array to 0 */ + /* CardClearFromHostDataSlot(card, slot); */ + } + + return CSR_RESULT_SUCCESS; +} /* process_clear_slot_command() */ + + +/* + * --------------------------------------------------------------------------- + * process_bulk_data_command + * + * Process a bulk data request from the UniFi. + * + * Arguments: + * card Pointer to card context struct + * bdcmd Pointer to bulk-data command msg from UniFi + * cmd, len Decoded values of command and length from the msg header + * Cmd will only be one of: + * SDIO_CMD_TO_HOST_TRANSFER + * SDIO_CMD_FROM_HOST_TRANSFER + * SDIO_CMD_FROM_HOST_AND_CLEAR + * SDIO_CMD_OVERLAY_TRANSFER + * + * Returns: + * CSR_RESULT_SUCCESS on success, CSR error code on error + * --------------------------------------------------------------------------- + */ +static CsrResult process_bulk_data_command(card_t *card, const u8 *cmdptr, + s16 cmd, u16 len) +{ + bulk_data_desc_t *bdslot; +#ifdef CSR_WIFI_ALIGNMENT_WORKAROUND + u8 *host_bulk_data_slot; +#endif + bulk_data_cmd_t bdcmd; + s16 offset; + s16 slot; + s16 dir; + CsrResult r; + + read_unpack_cmd(cmdptr, &bdcmd); + + unifi_trace(card->ospriv, UDBG4, "Processing bulk data cmd %d %s, len=%d, slot=0x%X\n", + cmd, lookup_bulkcmd_name(cmd), len, bdcmd.data_slot); + + /* + * Round up the transfer length if required. + * This is useful to force all transfers to be a multiple of the SDIO block + * size, so the SDIO driver won't try to use a byte-mode CMD53. These are + * broken on some hardware platforms. + */ + if (card->sdio_io_block_pad) + { + len = (len + card->sdio_io_block_size - 1) & ~(card->sdio_io_block_size - 1); + unifi_trace(card->ospriv, UDBG4, "Rounded bulk data length up to %d\n", len); + } + + slot = bdcmd.data_slot & 0x7FFF; + + if (cmd == SDIO_CMD_OVERLAY_TRANSFER) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; /* Not used on CSR6xxx */ + } + else + { + if (bdcmd.data_slot & SLOT_DIR_TO_HOST) + { + /* Request is for to-host bulk data */ + + /* Check sanity of slot number */ + if (slot >= card->config_data.num_tohost_data_slots) + { + unifi_error(card->ospriv, + "Invalid to-host data slot in SDIO bulk xfr req: %d\n", + slot); + return CSR_RESULT_FAILURE; + } + + /* Allocate memory for card->to_host_data[slot] bulk data here. */ +#ifdef CSR_PRE_ALLOC_NET_DATA + r = prealloc_netdata_get(card, &card->to_host_data[slot], len); +#else + r = unifi_net_data_malloc(card->ospriv, &card->to_host_data[slot], len); +#endif + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to allocate t-h bulk data\n"); + return CSR_RESULT_FAILURE; + } + + bdslot = &card->to_host_data[slot]; + + /* Make sure that the buffer is 4-bytes aligned */ + r = unifi_net_dma_align(card->ospriv, bdslot); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to align t-h bulk data buffer for DMA\n"); + return CSR_RESULT_FAILURE; + } + } + else + { + /* Request is for from-host bulk data */ + + if (slot >= card->config_data.num_fromhost_data_slots) + { + unifi_error(card->ospriv, + "Invalid from-host data slot in SDIO bulk xfr req: %d\n", + slot); + return CSR_RESULT_FAILURE; + } + bdslot = &card->from_host_data[slot].bd; + } + offset = bdcmd.offset; + } + /* Do the transfer */ + dir = (cmd == SDIO_CMD_TO_HOST_TRANSFER)? + UNIFI_SDIO_READ : UNIFI_SDIO_WRITE; + + unifi_trace(card->ospriv, UDBG4, + "Bulk %c %s len=%d, handle %d - slot=%d %p+(%d)\n", + (dir == UNIFI_SDIO_READ)?'R' : 'W', + lookup_bulkcmd_name(cmd), + len, + bdcmd.buffer_handle, + slot, bdslot->os_data_ptr, offset); +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "Bulk %s len=%d, handle %d - slot=%d %p+(%d)\n", + lookup_bulkcmd_name(cmd), + len, + bdcmd.buffer_handle, + slot, bdslot->os_data_ptr, offset); +#endif /* CSR_WIFI_HIP_NOISY */ + + + if (bdslot->os_data_ptr == NULL) + { + unifi_error(card->ospriv, "Null os_data_ptr - Bulk %s handle %d - slot=%d o=(%d)\n", + lookup_bulkcmd_name(cmd), + bdcmd.buffer_handle, + slot, + offset); + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + +#ifdef CSR_WIFI_ALIGNMENT_WORKAROUND + /* if os_data_ptr is not 4-byte aligned, then allocate a new buffer and copy data + to new buffer to ensure the address passed to unifi_bulk_rw is 4-byte aligned */ + + if (len != 0 && (dir == UNIFI_SDIO_WRITE) && (((ptrdiff_t)bdslot->os_data_ptr + offset) & 3)) + { + host_bulk_data_slot = kmalloc(len, GFP_KERNEL); + + if (!host_bulk_data_slot) + { + unifi_error(card->ospriv, " failed to allocate request_data before unifi_bulk_rw\n"); + return -1; + } + + memcpy((void *)host_bulk_data_slot, + (void *)(bdslot->os_data_ptr + offset), len); + + r = unifi_bulk_rw(card, + bdcmd.buffer_handle, + (void *)host_bulk_data_slot, + len, + dir); + } + else +#endif + { + r = unifi_bulk_rw(card, + bdcmd.buffer_handle, + (void *)(bdslot->os_data_ptr + offset), + len, + dir); + } + + if (r == CSR_WIFI_HIP_RESULT_NO_DEVICE) + { + return r; + } + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, + "Failed: %s hlen=%d, slen=%d, handle %d - slot=%d %p+0x%X\n", + lookup_bulkcmd_name(cmd), + len, /* Header length */ + bdslot->data_length, /* Length stored in slot */ + bdcmd.buffer_handle, + slot, bdslot->os_data_ptr, offset); + return r; + } + + bdslot->data_length = len; + + if (cmd == SDIO_CMD_FROM_HOST_AND_CLEAR) + { + if (slot >= card->config_data.num_fromhost_data_slots) + { + unifi_error(card->ospriv, + "Invalid from-host data slot in SDIO_CMD_FROM_HOST_AND_CLEAR: %d\n", + slot); + return CSR_RESULT_FAILURE; + } + +#ifdef CSR_WIFI_ALIGNMENT_WORKAROUND + /* moving this check before we clear host data slot */ + if ((len != 0) && (dir == UNIFI_SDIO_WRITE) && (((ptrdiff_t)bdslot->os_data_ptr + offset) & 3)) + { + kfree(host_bulk_data_slot); + } +#endif + + if (card->fh_slot_host_tag_record) + { + unifi_trace(card->ospriv, UDBG5, "CopyFromHostAndClearSlot Reset entry for slot=%d\n", slot); + + /* reset the host tag entry for the corresponding slot */ + card->fh_slot_host_tag_record[slot] = CSR_WIFI_HIP_RESERVED_HOST_TAG; + } + + + /* Set length field in from_host_data array to 0 */ + CardClearFromHostDataSlot(card, slot); + } + + return CSR_RESULT_SUCCESS; +} /* process_bulk_data_command() */ + + +/* + * --------------------------------------------------------------------------- + * check_fh_sig_slots + * + * Check whether there are <n> free signal slots available on UniFi. + * This takes into account the signals already batched since the + * from_host_signal counts were last read. + * If the from_host_signal counts indicate not enough space, we read + * the latest count from UniFi to see if some more have been freed. + * + * Arguments: + * None. + * + * Returns: + * CSR_RESULT_SUCCESS, otherwise CSR error code on error. + * --------------------------------------------------------------------------- + */ +static CsrResult check_fh_sig_slots(card_t *card, u16 needed, s32 *space_fh) +{ + u32 count_fhw; + u32 occupied_fh, slots_fh; + s32 count_fhr; + + count_fhw = card->from_host_signals_w; + count_fhr = card->from_host_signals_r; + slots_fh = card->config_data.num_fromhost_sig_frags; + + /* Only read the space in from-host queue if necessary */ + occupied_fh = (count_fhw - count_fhr) % 128; + + if (slots_fh < occupied_fh) + { + *space_fh = 0; + } + else + { + *space_fh = slots_fh - occupied_fh; + } + + if ((occupied_fh != 0) && (*space_fh < needed)) + { + count_fhr = unifi_read_shared_count(card, card->sdio_ctrl_addr + 2); + if (count_fhr < 0) + { + unifi_error(card->ospriv, "Failed to read from-host sig read count\n"); + return CSR_RESULT_FAILURE; + } + card->from_host_signals_r = count_fhr; /* diag */ + + occupied_fh = (count_fhw - count_fhr) % 128; + *space_fh = slots_fh - occupied_fh; + } + + return CSR_RESULT_SUCCESS; +} /* check_fh_sig_slots() */ + + +/* +* If we are padding the From-Host signals to the SDIO block size, +* we need to round up the needed_chunks to the SDIO block size. +*/ +#define ROUND_UP_NEEDED_CHUNKS(_card, _needed_chunks) \ + { \ + u16 _chunks_per_block; \ + u16 _chunks_in_last_block; \ + \ + if (_card->sdio_io_block_pad) \ + { \ + _chunks_per_block = _card->sdio_io_block_size / _card->config_data.sig_frag_size; \ + _chunks_in_last_block = _needed_chunks % _chunks_per_block; \ + if (_chunks_in_last_block != 0) \ + { \ + _needed_chunks = _needed_chunks + (_chunks_per_block - _chunks_in_last_block); \ + } \ + } \ + } + + +#define ROUND_UP_SPACE_CHUNKS(_card, _space_chunks) \ + { \ + u16 _chunks_per_block; \ + \ + if (_card->sdio_io_block_pad) \ + { \ + _chunks_per_block = _card->sdio_io_block_size / _card->config_data.sig_frag_size; \ + _space_chunks = ((_space_chunks / _chunks_per_block) * _chunks_per_block); \ + } \ + } + + + + + +/* + * --------------------------------------------------------------------------- + * process_fh_cmd_queue + * + * Take one signal off the from-host queue and copy it to the UniFi. + * Does nothing if the UniFi has no slots free. + * + * Arguments: + * card Pointer to card context struct + * processed Location to write: + * 0 if there is nothing on the queue to process + * 1 if a signal was successfully processed + * + * Returns: + * CSR error code if an error occurred. + * + * Notes: + * The from-host queue contains signal requests from the network driver + * and any UDI clients interspersed. UDI clients' requests have been stored + * in the from-host queue using the wire-format structures, as they arrive. + * All other requests are stored in the from-host queue using the host + * (cpu specific) structures. We use the is_packed member of the card_signal_t + * structure that describes the queue to make the distiction. + * --------------------------------------------------------------------------- + */ +static CsrResult process_fh_cmd_queue(card_t *card, s32 *processed) +{ + q_t *sigq = &card->fh_command_queue; + + CsrResult r; + u16 pending_sigs; + u16 pending_chunks; + u16 needed_chunks; + s32 space_chunks; + u16 q_index; + + *processed = 0; + + /* Get the number of pending signals. */ + pending_sigs = CSR_WIFI_HIP_Q_SLOTS_USED(sigq); + unifi_trace(card->ospriv, UDBG5, "proc_fh: %d pending\n", pending_sigs); + if (pending_sigs == 0) + { + /* Nothing to do */ + return CSR_RESULT_SUCCESS; + } + + /* Work out how many chunks we have waiting to send */ + for (pending_chunks = 0, q_index = CSR_WIFI_HIP_Q_NEXT_R_SLOT(sigq); + q_index != CSR_WIFI_HIP_Q_NEXT_W_SLOT(sigq); + q_index = CSR_WIFI_HIP_Q_WRAP(sigq, q_index + 1)) + { + card_signal_t *csptr = CSR_WIFI_HIP_Q_SLOT_DATA(sigq, q_index); + + /* + * Note that GET_CHUNKS_FOR() needs the size of the packed + * (wire-formatted) structure + */ + pending_chunks += GET_CHUNKS_FOR(card->config_data.sig_frag_size, (u16)(csptr->signal_length + 2)); + } + + /* + * Check whether UniFi has space for all the buffered bulk-data + * commands and signals as well. + */ + needed_chunks = pending_chunks + card->fh_buffer.count; + + /* Round up to the block size if necessary */ + ROUND_UP_NEEDED_CHUNKS(card, needed_chunks); + + r = check_fh_sig_slots(card, needed_chunks, &space_chunks); + if (r != CSR_RESULT_SUCCESS) + { + /* Error */ + unifi_error(card->ospriv, "Failed to read fh sig count\n"); + return r; + } + +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "proc_fh: %d chunks free, need %d\n", + space_chunks, needed_chunks); +#endif /* CSR_WIFI_HIP_NOISY */ + + + /* + * Coalesce as many from-host signals as possible + * into a single block and write using a single CMD53 + */ + if (needed_chunks > (u16)space_chunks) + { + /* Round up to the block size if necessary */ + ROUND_UP_SPACE_CHUNKS(card, space_chunks); + + /* + * If the f/w has less free chunks than those already pending + * return immediately. + */ + if ((u16)space_chunks <= card->fh_buffer.count) + { + /* + * No room in UniFi for any signals after the buffered bulk + * data commands have been sent. + */ + unifi_error(card->ospriv, "not enough room to send signals, need %d chunks, %d free\n", + card->fh_buffer.count, space_chunks); + card->generate_interrupt = 1; + return CSR_RESULT_SUCCESS; + } + pending_chunks = (u16)(space_chunks - card->fh_buffer.count); + } + + while (pending_sigs-- && pending_chunks > 0) + { + card_signal_t *csptr; + s16 i; + u16 sig_chunks, total_length, free_chunks_in_fh_buffer; + bulk_data_param_t bulkdata; + u8 *packed_sigptr; + u16 signal_length = 0; + + /* Retrieve the entry at the head of the queue */ + q_index = CSR_WIFI_HIP_Q_NEXT_R_SLOT(sigq); + + /* Get a pointer to the containing card_signal_t struct */ + csptr = CSR_WIFI_HIP_Q_SLOT_DATA(sigq, q_index); + + /* Get the new length of the packed signal */ + signal_length = csptr->signal_length; + + if ((signal_length & 1) || (signal_length > UNIFI_PACKED_SIGBUF_SIZE)) + { + unifi_error(card->ospriv, "process_fh_queue: Bad len: %d\n", signal_length); + return CSR_RESULT_FAILURE; + } + + /* Need space for 2-byte SDIO protocol header + signal */ + sig_chunks = GET_CHUNKS_FOR(card->config_data.sig_frag_size, (u16)(signal_length + 2)); + + free_chunks_in_fh_buffer = GET_CHUNKS_FOR(card->config_data.sig_frag_size, + (u16)((card->fh_buffer.buf + UNIFI_FH_BUF_SIZE) - card->fh_buffer.ptr)); + if (free_chunks_in_fh_buffer < sig_chunks) + { + /* No more room */ + unifi_notice(card->ospriv, "proc_fh_cmd_q: no room in fh buffer for 0x%.4X, deferring\n", + (u16)(GET_SIGNAL_ID(csptr->sigbuf))); + break; + } + + packed_sigptr = csptr->sigbuf; + + /* Claim and set up a from-host data slot */ + if (CSR_RESULT_FAILURE == CardWriteBulkData(card, csptr, UNIFI_TRAFFIC_Q_MLME)) + { + unifi_notice(card->ospriv, "proc_fh_cmd_q: no fh data slots for 0x%.4X, deferring\n", + (u16)(GET_SIGNAL_ID(csptr->sigbuf))); + break; + } + + for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; i++) + { + if (csptr->bulkdata[i].data_length == 0) + { + UNIFI_INIT_BULK_DATA(&bulkdata.d[i]); + } + else + { + bulkdata.d[i].os_data_ptr = csptr->bulkdata[i].os_data_ptr; + bulkdata.d[i].data_length = csptr->bulkdata[i].data_length; + } + + /* Pass the free responsibility to the lower layer. */ + UNIFI_INIT_BULK_DATA(&csptr->bulkdata[i]); + } + + unifi_trace(card->ospriv, UDBG2, "Sending signal 0x%.4X\n", + GET_SIGNAL_ID(packed_sigptr)); +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "Sending signal 0x%.4X\n", + GET_SIGNAL_ID(packed_sigptr)); +#endif /* CSR_WIFI_HIP_NOISY */ + + + /* Append packed signal to F-H buffer */ + total_length = sig_chunks * card->config_data.sig_frag_size; + + card->fh_buffer.ptr[0] = (u8)(signal_length & 0xff); + card->fh_buffer.ptr[1] = + (u8)(((signal_length >> 8) & 0xf) | (SDIO_CMD_SIGNAL << 4)); + + memcpy(card->fh_buffer.ptr + 2, packed_sigptr, signal_length); + memset(card->fh_buffer.ptr + 2 + signal_length, 0, + total_length - (2 + signal_length)); + +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "proc_fh: fh_buffer %d bytes \n", + signal_length + 2); + dump(card->fh_buffer.ptr, signal_length + 2); + unifi_trace(card->ospriv, UDBG1, " \n"); +#endif /* CSR_WIFI_HIP_NOISY */ + + card->fh_buffer.ptr += total_length; + card->fh_buffer.count += sig_chunks; + +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "Added %d to fh buf, len now %d, count %d\n", + signal_length, + card->fh_buffer.ptr - card->fh_buffer.buf, + card->fh_buffer.count); +#endif /* CSR_WIFI_HIP_NOISY */ + + (*processed)++; + pending_chunks -= sig_chunks; + + /* Log the signal to the UDI. */ + /* UDI will get the packed structure */ + /* Can not log the unpacked signal, unless we reconstruct it! */ + if (card->udi_hook) + { + (*card->udi_hook)(card->ospriv, packed_sigptr, signal_length, + &bulkdata, UDI_LOG_FROM_HOST); + } + + /* Remove entry from q */ + csptr->signal_length = 0; + CSR_WIFI_HIP_Q_INC_R(sigq); + } + + return CSR_RESULT_SUCCESS; +} /* process_fh_cmd_queue() */ + + +/* + * --------------------------------------------------------------------------- + * process_fh_traffic_queue + * + * Take signals off the from-host queue and copy them to the UniFi. + * Does nothing if the UniFi has no slots free. + * + * Arguments: + * card Pointer to card context struct + * sigq Pointer to the traffic queue + * processed Pointer to location to write: + * 0 if there is nothing on the queue to process + * 1 if a signal was successfully processed + * + * Returns: + * CSR error code if an error occurred. + * + * Notes: + * The from-host queue contains signal requests from the network driver + * and any UDI clients interspersed. + * --------------------------------------------------------------------------- + */ +static CsrResult process_fh_traffic_queue(card_t *card, s32 *processed) +{ + q_t *sigq = card->fh_traffic_queue; + + CsrResult r; + s16 n = 0; + s32 q_no; + u16 pending_sigs = 0; + u16 pending_chunks = 0; + u16 needed_chunks; + s32 space_chunks; + u16 q_index; + u32 host_tag = 0; + u16 slot_num = 0; + + *processed = 0; + + /* calculate how many signals are in queues and how many chunks are needed. */ + for (n = UNIFI_NO_OF_TX_QS - 1; n >= 0; n--) + { + /* Get the number of pending signals. */ + pending_sigs += CSR_WIFI_HIP_Q_SLOTS_USED(&sigq[n]); + unifi_trace(card->ospriv, UDBG5, "proc_fh%d: %d pending\n", n, pending_sigs); + + /* Work out how many chunks we have waiting to send */ + for (q_index = CSR_WIFI_HIP_Q_NEXT_R_SLOT(&sigq[n]); + q_index != CSR_WIFI_HIP_Q_NEXT_W_SLOT(&sigq[n]); + q_index = CSR_WIFI_HIP_Q_WRAP(&sigq[n], q_index + 1)) + { + card_signal_t *csptr = CSR_WIFI_HIP_Q_SLOT_DATA(&sigq[n], q_index); + + /* + * Note that GET_CHUNKS_FOR() needs the size of the packed + * (wire-formatted) structure + */ + pending_chunks += GET_CHUNKS_FOR(card->config_data.sig_frag_size, (u16)(csptr->signal_length + 2)); + } + } + + /* If there are no pending signals, just return */ + if (pending_sigs == 0) + { + /* Nothing to do */ + return CSR_RESULT_SUCCESS; + } + + /* + * Check whether UniFi has space for all the buffered bulk-data + * commands and signals as well. + */ + needed_chunks = pending_chunks + card->fh_buffer.count; + + /* Round up to the block size if necessary */ + ROUND_UP_NEEDED_CHUNKS(card, needed_chunks); + + r = check_fh_sig_slots(card, needed_chunks, &space_chunks); + if (r != CSR_RESULT_SUCCESS) + { + /* Error */ + unifi_error(card->ospriv, "Failed to read fh sig count\n"); + return r; + } + +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, + "process_fh_traffic_queue: %d chunks free, need %d\n", + space_chunks, needed_chunks); + read_fhsr(card); /* debugging only */ +#endif /* CSR_WIFI_HIP_NOISY */ + + /* Coalesce as many from-host signals as possible + into a single block and write using a single CMD53 */ + if (needed_chunks > (u16)space_chunks) + { + /* Round up to the block size if necessary */ + ROUND_UP_SPACE_CHUNKS(card, space_chunks); + + if ((u16)space_chunks <= card->fh_buffer.count) + { + /* + * No room in UniFi for any signals after the buffered bulk + * data commands have been sent. + */ + unifi_error(card->ospriv, "not enough room to send signals, need %d chunks, %d free\n", + card->fh_buffer.count, space_chunks); + card->generate_interrupt = 1; + return 0; + } + + pending_chunks = (u16)space_chunks - card->fh_buffer.count; + } + + q_no = UNIFI_NO_OF_TX_QS - 1; + + /* + * pending_sigs will be exhausted if there are is no restriction to the pending + * signals per queue. pending_chunks may be exhausted if there is a restriction. + * q_no check will be exhausted if there is a restriction and our round-robin + * algorith fails to fill all chunks. + */ + do + { + card_signal_t *csptr; + u16 sig_chunks, total_length, free_chunks_in_fh_buffer; + bulk_data_param_t bulkdata; + u8 *packed_sigptr; + u16 signal_length = 0; + + /* if this queue is empty go to next one. */ + if (CSR_WIFI_HIP_Q_SLOTS_USED(&sigq[q_no]) == 0) + { + q_no--; + continue; + } + + /* Retrieve the entry at the head of the queue */ + q_index = CSR_WIFI_HIP_Q_NEXT_R_SLOT(&sigq[q_no]); + + /* Get a pointer to the containing card_signal_t struct */ + csptr = CSR_WIFI_HIP_Q_SLOT_DATA(&sigq[q_no], q_index); + + /* Get the new length of the packed signal */ + signal_length = csptr->signal_length; + + if ((signal_length & 1) || (signal_length > UNIFI_PACKED_SIGBUF_SIZE)) + { + unifi_error(card->ospriv, "process_fh_traffic_queue: Bad len: %d\n", signal_length); + return CSR_RESULT_FAILURE; + } + + /* Need space for 2-byte SDIO protocol header + signal */ + sig_chunks = GET_CHUNKS_FOR(card->config_data.sig_frag_size, (u16)(signal_length + 2)); + free_chunks_in_fh_buffer = GET_CHUNKS_FOR(card->config_data.sig_frag_size, + (u16)((card->fh_buffer.buf + UNIFI_FH_BUF_SIZE) - card->fh_buffer.ptr)); + if (free_chunks_in_fh_buffer < sig_chunks) + { + /* No more room */ + unifi_notice(card->ospriv, "process_fh_traffic_queue: no more chunks.\n"); + break; + } + + packed_sigptr = csptr->sigbuf; + /* Claim and set up a from-host data slot */ + if (CSR_RESULT_FAILURE == CardWriteBulkData(card, csptr, (unifi_TrafficQueue)q_no)) + { + q_no--; + continue; + } + + /* Sanity check: MA-PACKET.req must have a valid bulk data */ + if ((csptr->bulkdata[0].data_length == 0) || (csptr->bulkdata[0].os_data_ptr == NULL)) + { + unifi_error(card->ospriv, "MA-PACKET.req with empty bulk data (%d bytes in %p)\n", + csptr->bulkdata[0].data_length, csptr->bulkdata[0].os_data_ptr); + dump(packed_sigptr, signal_length); + return CSR_RESULT_FAILURE; + } + + bulkdata.d[0].os_data_ptr = csptr->bulkdata[0].os_data_ptr; + bulkdata.d[0].data_length = csptr->bulkdata[0].data_length; + bulkdata.d[0].os_net_buf_ptr = csptr->bulkdata[0].os_net_buf_ptr; + bulkdata.d[0].net_buf_length = csptr->bulkdata[0].net_buf_length; + + /* The driver owns clearing of HIP slots for following scenario + * - driver has requested a MA-PACKET.req signal + * - The f/w after receiving the signal decides it can't send it out due to various reasons + * - So the f/w without downloading the bulk data decides to just send a confirmation with fail + * - and then sends a clear slot signal to HIP + * + * But in some cases the clear slot signal never comes and the slot remains --NOT-- freed for ever + * + * To handle this, HIP will keep the record of host tag for each occupied slot + * and then based on status of that Host tag and slot the driver will decide if the slot is + * cleared by f/w signal or the slot has to be freed by driver + */ + + if (card->fh_slot_host_tag_record) + { + /* Update the f-h slot record for the corresponding host tag */ + host_tag = GET_PACKED_MA_PACKET_REQUEST_HOST_TAG(packed_sigptr); + slot_num = GET_PACKED_DATAREF_SLOT(packed_sigptr, 0) & 0x00FF; + + unifi_trace(card->ospriv, UDBG5, + "process_fh_traffic_queue signal ID =%x fh slot=%x Host tag =%x\n", + GET_SIGNAL_ID(packed_sigptr), slot_num, host_tag); + card->fh_slot_host_tag_record[slot_num] = host_tag; + } + UNIFI_INIT_BULK_DATA(&bulkdata.d[1]); + UNIFI_INIT_BULK_DATA(&csptr->bulkdata[0]); + UNIFI_INIT_BULK_DATA(&csptr->bulkdata[1]); + +#ifdef CSR_WIFI_HIP_DATA_PLANE_PROFILE + if (bulkdata.d[0].os_data_ptr) + { + if ((*bulkdata.d[0].os_data_ptr) & 0x08) + { + card->cmd_prof.tx_count++; + } + } +#endif + unifi_trace(card->ospriv, UDBG3, "Sending signal 0x%.4X\n", + GET_SIGNAL_ID(packed_sigptr)); +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "Sending signal 0x%.4X\n", + GET_SIGNAL_ID(packed_sigptr)); +#endif /* CSR_WIFI_HIP_NOISY */ + + /* Append packed signal to F-H buffer */ + total_length = sig_chunks * card->config_data.sig_frag_size; + + card->fh_buffer.ptr[0] = (u8)(signal_length & 0xff); + card->fh_buffer.ptr[1] = + (u8)(((signal_length >> 8) & 0xf) | (SDIO_CMD_SIGNAL << 4)); + + memcpy(card->fh_buffer.ptr + 2, packed_sigptr, signal_length); + memset(card->fh_buffer.ptr + 2 + signal_length, 0, + total_length - (2 + signal_length)); + +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "proc_fh: fh_buffer %d bytes \n", + signal_length + 2); + dump(card->fh_buffer.ptr, signal_length + 2); + unifi_trace(card->ospriv, UDBG1, " \n"); +#endif /* CSR_WIFI_HIP_NOISY */ + + card->fh_buffer.ptr += total_length; + card->fh_buffer.count += sig_chunks; + +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "Added %d to fh buf, len now %d, count %d\n", + signal_length, + card->fh_buffer.ptr - card->fh_buffer.buf, + card->fh_buffer.count); +#endif /* CSR_WIFI_HIP_NOISY */ + + (*processed)++; + pending_sigs--; + pending_chunks -= sig_chunks; + + /* Log the signal to the UDI. */ + /* UDI will get the packed structure */ + /* Can not log the unpacked signal, unless we reconstruct it! */ + if (card->udi_hook) + { + (*card->udi_hook)(card->ospriv, packed_sigptr, signal_length, + &bulkdata, UDI_LOG_FROM_HOST); + } + + /* Remove entry from q */ + csptr->signal_length = 0; + /* Note that the traffic queue has only one valid bulk data buffer. */ + csptr->bulkdata[0].data_length = 0; + + CSR_WIFI_HIP_Q_INC_R(&sigq[q_no]); + } while ((pending_sigs > 0) && (pending_chunks > 0) && (q_no >= 0)); + + return CSR_RESULT_SUCCESS; +} /* process_fh_traffic_queue() */ + + +/* + * --------------------------------------------------------------------------- + * flush_fh_buffer + * + * Write out the cache from-hosts signals to the UniFi. + * + * Arguments: + * card Pointer to card context struct + * + * Returns: + * CSR error code if an SDIO error occurred. + * --------------------------------------------------------------------------- + */ +static CsrResult flush_fh_buffer(card_t *card) +{ + CsrResult r; + u16 len; + u16 sig_units; + u16 data_round; + u16 chunks_in_last_block; + u16 padding_chunks; + u16 i; + + len = card->fh_buffer.ptr - card->fh_buffer.buf; + +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "fh_buffer is at %p, ptr= %p\n", + card->fh_buffer.buf, card->fh_buffer.ptr); +#endif /* CSR_WIFI_HIP_NOISY */ + + if (len == 0) + { + return CSR_RESULT_SUCCESS; + } + +#ifdef CSR_WIFI_HIP_NOISY + if (dump_fh_buf) + { + dump(card->fh_buffer.buf, len); + dump_fh_buf = 0; + } +#endif /* CSR_WIFI_HIP_NOISY */ + + if (card->sdio_io_block_pad) + { + /* Both of these are powers of 2 */ + sig_units = card->config_data.sig_frag_size; + data_round = card->sdio_io_block_size; + + if (data_round > sig_units) + { + chunks_in_last_block = (len % data_round) / sig_units; + + if (chunks_in_last_block != 0) + { + padding_chunks = (data_round / sig_units) - chunks_in_last_block; + + memset(card->fh_buffer.ptr, 0, padding_chunks * sig_units); + for (i = 0; i < padding_chunks; i++) + { + card->fh_buffer.ptr[1] = SDIO_CMD_PADDING << 4; + card->fh_buffer.ptr += sig_units; + } + + card->fh_buffer.count += padding_chunks; + len += padding_chunks * sig_units; + } + } + } + + r = unifi_bulk_rw(card, + card->config_data.fromhost_sigbuf_handle, + card->fh_buffer.buf, + len, UNIFI_SDIO_WRITE); + if (r == CSR_WIFI_HIP_RESULT_NO_DEVICE) + { + return r; + } + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to write fh signals: %u bytes, error %d\n", len, r); + return r; + } + + /* Update from-host-signals-written signal count */ + card->from_host_signals_w = + (card->from_host_signals_w + card->fh_buffer.count) % 128u; + r = unifi_write_8_or_16(card, card->sdio_ctrl_addr + 0, + (u8)card->from_host_signals_w); + if (r != CSR_RESULT_SUCCESS) + { + unifi_error(card->ospriv, "Failed to write fh signal count %u with error %d\n", + card->from_host_signals_w, r); + return r; + } + card->generate_interrupt = 1; + + /* Reset the fh buffer pointer */ + card->fh_buffer.ptr = card->fh_buffer.buf; + card->fh_buffer.count = 0; + +#ifdef CSR_WIFI_HIP_NOISY + unifi_error(card->ospriv, "END flush: fh len %d, count %d\n", + card->fh_buffer.ptr - card->fh_buffer.buf, + card->fh_buffer.count); +#endif /* CSR_WIFI_HIP_NOISY */ + + return CSR_RESULT_SUCCESS; +} /* flush_fh_buffer() */ + + +/* + * --------------------------------------------------------------------------- + * restart_packet_flow + * + * This function is called before the bottom-half thread sleeps. + * It checks whether both data and signal resources are available and + * then calls the OS-layer function to re-enable packet transmission. + * + * Arguments: + * card Pointer to card context struct + * + * Returns: + * None. + * --------------------------------------------------------------------------- + */ +static void restart_packet_flow(card_t *card) +{ + u8 q; + + /* + * We only look at the fh_traffic_queue, because that is where packets from + * the network stack are placed. + */ + for (q = 0; q <= UNIFI_TRAFFIC_Q_VO; q++) + { + if (card_is_tx_q_paused(card, q) && + CSR_WIFI_HIP_Q_SLOTS_FREE(&card->fh_traffic_queue[q]) >= RESUME_XMIT_THRESHOLD) + { +#if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) && defined (CSR_WIFI_HIP_DATA_PLANE_PROFILE) + unifi_debug_log_to_buf("U"); +#endif + card_tx_q_unpause(card, q); + unifi_restart_xmit(card->ospriv, (unifi_TrafficQueue)q); + } + } +} /* restart_packet_flow() */ + + |