| /***************************************************************************** |
| |
| (c) Cambridge Silicon Radio Limited 2011 |
| 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_send.c |
| * |
| * PURPOSE: |
| * Code for adding a signal request to the from-host queue. |
| * When the driver bottom-half is run, it will take requests from the |
| * queue and pass them to the UniFi. |
| * |
| * *************************************************************************** |
| */ |
| #include "csr_wifi_hip_unifi.h" |
| #include "csr_wifi_hip_conversions.h" |
| #include "csr_wifi_hip_sigs.h" |
| #include "csr_wifi_hip_card.h" |
| |
| unifi_TrafficQueue unifi_frame_priority_to_queue(CSR_PRIORITY priority) |
| { |
| switch (priority) |
| { |
| case CSR_QOS_UP0: |
| case CSR_QOS_UP3: |
| return UNIFI_TRAFFIC_Q_BE; |
| case CSR_QOS_UP1: |
| case CSR_QOS_UP2: |
| return UNIFI_TRAFFIC_Q_BK; |
| case CSR_QOS_UP4: |
| case CSR_QOS_UP5: |
| return UNIFI_TRAFFIC_Q_VI; |
| case CSR_QOS_UP6: |
| case CSR_QOS_UP7: |
| case CSR_MANAGEMENT: |
| return UNIFI_TRAFFIC_Q_VO; |
| default: |
| return UNIFI_TRAFFIC_Q_BE; |
| } |
| } |
| |
| |
| CSR_PRIORITY unifi_get_default_downgrade_priority(unifi_TrafficQueue queue) |
| { |
| switch (queue) |
| { |
| case UNIFI_TRAFFIC_Q_BE: |
| return CSR_QOS_UP0; |
| case UNIFI_TRAFFIC_Q_BK: |
| return CSR_QOS_UP1; |
| case UNIFI_TRAFFIC_Q_VI: |
| return CSR_QOS_UP5; |
| case UNIFI_TRAFFIC_Q_VO: |
| return CSR_QOS_UP6; |
| default: |
| return CSR_QOS_UP0; |
| } |
| } |
| |
| |
| /* |
| * --------------------------------------------------------------------------- |
| * send_signal |
| * |
| * This function queues a signal for sending to UniFi. It first checks |
| * that there is space on the fh_signal_queue for another entry, then |
| * claims any bulk data slots required and copies data into them. Then |
| * increments the fh_signal_queue write count. |
| * |
| * The fh_signal_queue is later processed by the driver bottom half |
| * (in unifi_bh()). |
| * |
| * This function call unifi_pause_xmit() to pause the flow of data plane |
| * packets when: |
| * - the fh_signal_queue ring buffer is full |
| * - there are less than UNIFI_MAX_DATA_REFERENCES (2) bulk data |
| * slots available. |
| * |
| * Arguments: |
| * card Pointer to card context structure |
| * sigptr Pointer to the signal to write to UniFi. |
| * siglen Number of bytes pointer to by sigptr. |
| * bulkdata Array of pointers to an associated bulk data. |
| * sigq To which from-host queue to add the signal. |
| * |
| * Returns: |
| * CSR_RESULT_SUCCESS on success |
| * CSR_WIFI_HIP_RESULT_NO_SPACE if there were insufficient data slots or |
| * no free signal queue entry |
| * |
| * Notes: |
| * Calls unifi_pause_xmit() when the last slots are used. |
| * --------------------------------------------------------------------------- |
| */ |
| static CsrResult send_signal(card_t *card, const u8 *sigptr, u32 siglen, |
| const bulk_data_param_t *bulkdata, |
| q_t *sigq, u32 priority_q, u32 run_bh) |
| { |
| u16 i, data_slot_size; |
| card_signal_t *csptr; |
| s16 qe; |
| CsrResult r; |
| s16 debug_print = 0; |
| |
| data_slot_size = CardGetDataSlotSize(card); |
| |
| /* Check that the fh_data_queue has a free slot */ |
| if (!CSR_WIFI_HIP_Q_SLOTS_FREE(sigq)) |
| { |
| unifi_trace(card->ospriv, UDBG3, "send_signal: %s full\n", sigq->name); |
| |
| return CSR_WIFI_HIP_RESULT_NO_SPACE; |
| } |
| |
| /* |
| * Now add the signal to the From Host signal queue |
| */ |
| /* Get next slot on queue */ |
| qe = CSR_WIFI_HIP_Q_NEXT_W_SLOT(sigq); |
| csptr = CSR_WIFI_HIP_Q_SLOT_DATA(sigq, qe); |
| |
| /* Make up the card_signal struct */ |
| csptr->signal_length = (u16)siglen; |
| memcpy((void *)csptr->sigbuf, (void *)sigptr, siglen); |
| |
| for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; ++i) |
| { |
| if ((bulkdata != NULL) && (bulkdata->d[i].data_length != 0)) |
| { |
| u32 datalen = bulkdata->d[i].data_length; |
| |
| /* Make sure data will fit in a bulk data slot */ |
| if (bulkdata->d[i].os_data_ptr == NULL) |
| { |
| unifi_error(card->ospriv, "send_signal - NULL bulkdata[%d]\n", i); |
| debug_print++; |
| csptr->bulkdata[i].data_length = 0; |
| } |
| else |
| { |
| if (datalen > data_slot_size) |
| { |
| unifi_error(card->ospriv, |
| "send_signal - Invalid data length %u (@%p), " |
| "truncating\n", |
| datalen, bulkdata->d[i].os_data_ptr); |
| datalen = data_slot_size; |
| debug_print++; |
| } |
| /* Store the bulk data info in the soft queue. */ |
| csptr->bulkdata[i].os_data_ptr = (u8 *)bulkdata->d[i].os_data_ptr; |
| csptr->bulkdata[i].os_net_buf_ptr = (u8 *)bulkdata->d[i].os_net_buf_ptr; |
| csptr->bulkdata[i].net_buf_length = bulkdata->d[i].net_buf_length; |
| csptr->bulkdata[i].data_length = datalen; |
| } |
| } |
| else |
| { |
| UNIFI_INIT_BULK_DATA(&csptr->bulkdata[i]); |
| } |
| } |
| |
| if (debug_print) |
| { |
| const u8 *sig = sigptr; |
| |
| unifi_error(card->ospriv, "Signal(%d): %*ph\n", siglen, |
| 16, sig); |
| unifi_error(card->ospriv, "Bulkdata pointer %p(%d), %p(%d)\n", |
| bulkdata != NULL?bulkdata->d[0].os_data_ptr : NULL, |
| bulkdata != NULL?bulkdata->d[0].data_length : 0, |
| bulkdata != NULL?bulkdata->d[1].os_data_ptr : NULL, |
| bulkdata != NULL?bulkdata->d[1].data_length : 0); |
| } |
| |
| /* Advance the written count to say there is a new entry */ |
| CSR_WIFI_HIP_Q_INC_W(sigq); |
| |
| /* |
| * Set the flag to say reason for waking was a host request. |
| * Then ask the OS layer to run the unifi_bh. |
| */ |
| if (run_bh == 1) |
| { |
| card->bh_reason_host = 1; |
| r = unifi_run_bh(card->ospriv); |
| if (r != CSR_RESULT_SUCCESS) |
| { |
| unifi_error(card->ospriv, "failed to run bh.\n"); |
| card->bh_reason_host = 0; |
| |
| /* |
| * The bulk data buffer will be freed by the caller. |
| * We need to invalidate the description of the bulk data in our |
| * soft queue, to prevent the core freeing the bulk data again later. |
| */ |
| for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; ++i) |
| { |
| if (csptr->bulkdata[i].data_length != 0) |
| { |
| csptr->bulkdata[i].os_data_ptr = csptr->bulkdata[i].os_net_buf_ptr = NULL; |
| csptr->bulkdata[i].net_buf_length = csptr->bulkdata[i].data_length = 0; |
| } |
| } |
| return r; |
| } |
| } |
| else |
| { |
| unifi_error(card->ospriv, "run_bh=%d, bh not called.\n", run_bh); |
| } |
| |
| /* |
| * Have we used up all the fh signal list entries? |
| */ |
| if (CSR_WIFI_HIP_Q_SLOTS_FREE(sigq) == 0) |
| { |
| /* We have filled the queue, so stop the upper layer. The command queue |
| * is an exception, as suspending due to that being full could delay |
| * resume/retry until new commands or data are received. |
| */ |
| if (sigq != &card->fh_command_queue) |
| { |
| /* |
| * Must call unifi_pause_xmit() *before* setting the paused flag. |
| * (the unifi_pause_xmit call should not be after setting the flag because of the possibility of being interrupted |
| * by the bh thread between our setting the flag and the call to unifi_pause_xmit() |
| * If bh thread then cleared the flag, we would end up paused, but without the flag set) |
| * Instead, setting it afterwards means that if this thread is interrupted by the bh thread |
| * the pause flag is still guaranteed to end up set |
| * However the potential deadlock now is that if bh thread emptied the queue and cleared the flag before this thread's |
| * call to unifi_pause_xmit(), then bh thread may not run again because it will be waiting for |
| * a packet to appear in the queue but nothing ever will because xmit is paused. |
| * So we will end up with the queue paused, and the flag set to say it is paused, but bh never runs to unpause it. |
| * (Note even this bad situation would not persist long in practice, because something else (eg rx, or tx in different queue) |
| * is likely to wake bh thread quite soon) |
| * But to avoid this deadlock completely, after setting the flag we check that there is something left in the queue. |
| * If there is, we know that bh thread has not emptied the queue yet. |
| * Since bh thread checks to unpause the queue *after* taking packets from the queue, we know that it is still going to make at |
| * least one more check to see whether it needs to unpause the queue. So all is well. |
| * If there are no packets in the queue, then the deadlock described above might happen. To make sure it does not, we |
| * unpause the queue here. A possible side effect is that unifi_restart_xmit() may (rarely) be called for second time |
| * unnecessarily, which is harmless |
| */ |
| |
| #if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) && defined (CSR_WIFI_HIP_DATA_PLANE_PROFILE) |
| unifi_debug_log_to_buf("P"); |
| #endif |
| unifi_pause_xmit(card->ospriv, (unifi_TrafficQueue)priority_q); |
| card_tx_q_pause(card, priority_q); |
| if (CSR_WIFI_HIP_Q_SLOTS_USED(sigq) == 0) |
| { |
| card_tx_q_unpause(card, priority_q); |
| unifi_restart_xmit(card->ospriv, (unifi_TrafficQueue) priority_q); |
| } |
| } |
| else |
| { |
| unifi_warning(card->ospriv, |
| "send_signal: fh_cmd_q full, not pausing (run_bh=%d)\n", |
| run_bh); |
| } |
| } |
| |
| return CSR_RESULT_SUCCESS; |
| } /* send_signal() */ |
| |
| |
| /* |
| * --------------------------------------------------------------------------- |
| * unifi_send_signal |
| * |
| * Invokes send_signal() to queue a signal in the command or traffic queue |
| * If sigptr pointer is NULL, it pokes the bh to check if UniFi is responsive. |
| * |
| * Arguments: |
| * card Pointer to card context struct |
| * sigptr Pointer to signal from card. |
| * siglen Size of the signal |
| * bulkdata Pointer to the bulk data of the signal |
| * |
| * Returns: |
| * CSR_RESULT_SUCCESS on success |
| * CSR_WIFI_HIP_RESULT_NO_SPACE if there were insufficient data slots or no free signal queue entry |
| * |
| * Notes: |
| * unifi_send_signal() is used to queue signals, created by the driver, |
| * to the device. Signals are constructed using the UniFi packed structures. |
| * --------------------------------------------------------------------------- |
| */ |
| CsrResult unifi_send_signal(card_t *card, const u8 *sigptr, u32 siglen, |
| const bulk_data_param_t *bulkdata) |
| { |
| q_t *sig_soft_q; |
| u16 signal_id; |
| CsrResult r; |
| u32 run_bh; |
| u32 priority_q; |
| |
| /* A NULL signal pointer is a request to check if UniFi is responsive */ |
| if (sigptr == NULL) |
| { |
| card->bh_reason_host = 1; |
| return unifi_run_bh(card->ospriv); |
| } |
| |
| priority_q = 0; |
| run_bh = 1; |
| signal_id = GET_SIGNAL_ID(sigptr); |
| /* |
| * If the signal is a CSR_MA_PACKET_REQUEST , |
| * we send it using the traffic soft queue. Else we use the command soft queue. |
| */ |
| if (signal_id == CSR_MA_PACKET_REQUEST_ID) |
| { |
| u16 frame_priority; |
| |
| if (card->periodic_wake_mode == UNIFI_PERIODIC_WAKE_HOST_ENABLED) |
| { |
| run_bh = 0; |
| } |
| |
| #if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) && defined (CSR_WIFI_HIP_DATA_PLANE_PROFILE) |
| unifi_debug_log_to_buf("D"); |
| #endif |
| /* Sanity check: MA-PACKET.req must have a valid bulk data */ |
| if ((bulkdata->d[0].data_length == 0) || (bulkdata->d[0].os_data_ptr == NULL)) |
| { |
| unifi_error(card->ospriv, "MA-PACKET.req with empty bulk data (%d bytes in %p)\n", |
| bulkdata->d[0].data_length, bulkdata->d[0].os_data_ptr); |
| dump((void *)sigptr, siglen); |
| return CSR_RESULT_FAILURE; |
| } |
| |
| /* Map the frame priority to a traffic queue index. */ |
| frame_priority = GET_PACKED_MA_PACKET_REQUEST_FRAME_PRIORITY(sigptr); |
| priority_q = unifi_frame_priority_to_queue((CSR_PRIORITY)frame_priority); |
| |
| sig_soft_q = &card->fh_traffic_queue[priority_q]; |
| } |
| else |
| { |
| sig_soft_q = &card->fh_command_queue; |
| } |
| |
| r = send_signal(card, sigptr, siglen, bulkdata, sig_soft_q, priority_q, run_bh); |
| /* On error, the caller must free or requeue bulkdata buffers */ |
| |
| return r; |
| } /* unifi_send_signal() */ |
| |
| |
| /* |
| * --------------------------------------------------------------------------- |
| * unifi_send_resources_available |
| * |
| * Examines whether there is available space to queue |
| * a signal in the command or traffic queue |
| * |
| * Arguments: |
| * card Pointer to card context struct |
| * sigptr Pointer to signal. |
| * |
| * Returns: |
| * CSR_RESULT_SUCCESS if resources available |
| * CSR_WIFI_HIP_RESULT_NO_SPACE if there was no free signal queue entry |
| * |
| * Notes: |
| * --------------------------------------------------------------------------- |
| */ |
| CsrResult unifi_send_resources_available(card_t *card, const u8 *sigptr) |
| { |
| q_t *sig_soft_q; |
| u16 signal_id = GET_SIGNAL_ID(sigptr); |
| |
| /* |
| * If the signal is a CSR_MA_PACKET_REQUEST , |
| * we send it using the traffic soft queue. Else we use the command soft queue. |
| */ |
| if (signal_id == CSR_MA_PACKET_REQUEST_ID) |
| { |
| u16 frame_priority; |
| u32 priority_q; |
| |
| /* Map the frame priority to a traffic queue index. */ |
| frame_priority = GET_PACKED_MA_PACKET_REQUEST_FRAME_PRIORITY(sigptr); |
| priority_q = unifi_frame_priority_to_queue((CSR_PRIORITY)frame_priority); |
| |
| sig_soft_q = &card->fh_traffic_queue[priority_q]; |
| } |
| else |
| { |
| sig_soft_q = &card->fh_command_queue; |
| } |
| |
| /* Check that the fh_data_queue has a free slot */ |
| if (!CSR_WIFI_HIP_Q_SLOTS_FREE(sig_soft_q)) |
| { |
| unifi_notice(card->ospriv, "unifi_send_resources_available: %s full\n", |
| sig_soft_q->name); |
| return CSR_WIFI_HIP_RESULT_NO_SPACE; |
| } |
| |
| return CSR_RESULT_SUCCESS; |
| } /* unifi_send_resources_available() */ |
| |
| |