aboutsummaryrefslogtreecommitdiff
path: root/drivers/staging/csr/csr_wifi_hip_send.c
blob: 76429e5e77cfa3857eddf8e9fd25e988211719a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
/*****************************************************************************

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