1 /*
2  * Copyright 2022, Cypress Semiconductor Corporation (an Infineon company)
3  * SPDX-License-Identifier: Apache-2.0
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /** @file
19  *  Allows thread safe access to the WHD WiFi Driver (WHD) hardware bus
20  *
21  *  This file provides functions which allow multiple threads to use the WHD hardware bus (SDIO or SPI)
22  *  This is achieved by having a single thread (the "WHD Thread") which queues messages to be sent, sending
23  *  them sequentially, as well as receiving messages as they arrive.
24  *
25  *  Messages to be sent come from the @ref whd_sdpcm_send_common function in whd_sdpcm.c .  The messages already
26  *  contain SDPCM headers, but not any bus headers (GSPI), and are passed to the whd_thread_send_data function.
27  *  This function can be called from any thread.
28  *
29  *  Messages are received by way of a callback supplied by in SDPCM.c - whd_sdpcm_process_rx_packet
30  *  Received messages are delivered in the context of the WHD Thread, so the callback function needs to avoid blocking.
31  *
32  *  It is also possible to use these functions without any operating system, by periodically calling the whd_thread_send_one_packet,
33  *  @ref whd_thread_receive_one_packet or @ref whd_thread_poll_all functions
34  *
35  */
36 #include "stdlib.h"
37 #include "whd_debug.h"
38 #include "whd_thread.h"
39 #include "bus_protocols/whd_bus_protocol_interface.h"
40 #include "cyabs_rtos.h"
41 #include "whd_int.h"
42 #include "whd_chip.h"
43 #include "whd_poll.h"
44 #include "whd_sdpcm.h"
45 #include "whd_buffer_api.h"
46 #include "whd_chip_constants.h"
47 
48 /******************************************************
49 *             Static Function Prototypes
50 ******************************************************/
51 static void whd_thread_func(cy_thread_arg_t thread_input);
52 
53 /******************************************************
54 *             Global Functions
55 ******************************************************/
whd_thread_info_init(whd_driver_t whd_driver,whd_init_config_t * whd_init_config)56 void whd_thread_info_init(whd_driver_t whd_driver, whd_init_config_t *whd_init_config)
57 {
58     memset(&whd_driver->thread_info, 0, sizeof(whd_driver->thread_info) );
59     whd_driver->thread_info.thread_stack_start = whd_init_config->thread_stack_start;
60     whd_driver->thread_info.thread_stack_size = whd_init_config->thread_stack_size;
61     whd_driver->thread_info.thread_priority = (cy_thread_priority_t)whd_init_config->thread_priority;
62 }
63 
64 /** Initialises the WHD Thread
65  *
66  * Initialises the WHD thread, and its flags/semaphores,
67  * then starts it running
68  *
69  * @return    WHD_SUCCESS : if initialisation succeeds
70  *            otherwise, a result code
71  */
whd_thread_init(whd_driver_t whd_driver)72 whd_result_t whd_thread_init(whd_driver_t whd_driver)
73 {
74     whd_result_t retval;
75 
76     retval = whd_sdpcm_init(whd_driver);
77 
78     if (retval != WHD_SUCCESS)
79     {
80         WPRINT_WHD_ERROR( ("Could not initialize SDPCM codec\n") );
81         /* Lint: Reachable after hitting assert & globals not defined due to error */
82         return retval;
83     }
84 
85     /* Create the event flag which signals the WHD thread needs to wake up */
86     retval = cy_rtos_init_semaphore(&whd_driver->thread_info.transceive_semaphore, 1, 0);
87     if (retval != WHD_SUCCESS)
88     {
89         WPRINT_WHD_ERROR( ("Could not initialize WHD thread semaphore\n") );
90         /* Lint: Reachable after hitting assert & globals not defined due to error */
91         return retval;
92     }
93 
94     retval = cy_rtos_create_thread(&whd_driver->thread_info.whd_thread, (cy_thread_entry_fn_t)whd_thread_func,
95                                    "WHD", whd_driver->thread_info.thread_stack_start,
96                                    whd_driver->thread_info.thread_stack_size,
97                                    whd_driver->thread_info.thread_priority, (cy_thread_arg_t)whd_driver);
98     if (retval != WHD_SUCCESS)
99     {
100         /* Could not start WHD main thread */
101         WPRINT_WHD_ERROR( ("Could not start WHD thread\n") );
102         return retval;
103     }
104 
105     /* Ready now. Indicate it here and in thread, whatever be executed first. */
106     whd_driver->thread_info.whd_inited = WHD_TRUE;
107 
108     return WHD_SUCCESS;
109 }
110 
111 /** Sends the first queued packet
112  *
113  * Checks the queue to determine if there is any packets waiting
114  * to be sent. If there are, then it sends the first one.
115  *
116  * This function is normally used by the WHD Thread, but can be
117  * called periodically by systems which have no RTOS to ensure
118  * packets get sent.
119  *
120  * @return    1 : packet was sent
121  *            0 : no packet sent
122  */
whd_thread_send_one_packet(whd_driver_t whd_driver)123 int8_t whd_thread_send_one_packet(whd_driver_t whd_driver)
124 {
125     whd_buffer_t tmp_buf_hnd = NULL;
126     whd_result_t result;
127 
128     if (whd_sdpcm_get_packet_to_send(whd_driver, &tmp_buf_hnd) != WHD_SUCCESS)
129     {
130         /* Failed to get a packet */
131         return 0;
132     }
133 
134     /* Ensure the wlan backplane bus is up */
135     result = whd_ensure_wlan_bus_is_up(whd_driver);
136     if (result != WHD_SUCCESS)
137     {
138         whd_assert("Could not bring bus back up", 0 != 0);
139         CHECK_RETURN(whd_buffer_release(whd_driver, tmp_buf_hnd, WHD_NETWORK_TX) );
140         return 0;
141     }
142 
143     WPRINT_WHD_DATA_LOG( ("Wcd:> Sending pkt 0x%08lX\n", (unsigned long)tmp_buf_hnd) );
144     if (whd_bus_send_buffer(whd_driver, tmp_buf_hnd) != WHD_SUCCESS)
145     {
146         WHD_STATS_INCREMENT_VARIABLE(whd_driver, tx_fail);
147         return 0;
148     }
149 
150     WHD_STATS_INCREMENT_VARIABLE(whd_driver, tx_total);
151     return (int8_t)1;
152 }
153 
154 /** Receives a packet if one is waiting
155  *
156  * Checks the wifi chip fifo to determine if there is any packets waiting
157  * to be received. If there are, then it receives the first one, and calls
158  * the callback @ref whd_sdpcm_process_rx_packet (in whd_sdpcm.c).
159  *
160  * This function is normally used by the WHD Thread, but can be
161  * called periodically by systems which have no RTOS to ensure
162  * packets get received properly.
163  *
164  * @return    1 : packet was received
165  *            0 : no packet waiting
166  */
whd_thread_receive_one_packet(whd_driver_t whd_driver)167 int8_t whd_thread_receive_one_packet(whd_driver_t whd_driver)
168 {
169     /* Check if there is a packet ready to be received */
170     whd_buffer_t recv_buffer;
171     if (whd_bus_read_frame(whd_driver, &recv_buffer) != WHD_SUCCESS)
172     {
173         /* Failed to read a packet */
174         return 0;
175     }
176 
177     if (recv_buffer != NULL)    /* Could be null if it was only a credit update */
178     {
179 
180         WPRINT_WHD_DATA_LOG( ("Wcd:< Rcvd pkt 0x%08lX\n", (unsigned long)recv_buffer) );
181         WHD_STATS_INCREMENT_VARIABLE(whd_driver, rx_total);
182 
183         /* Send received buffer up to SDPCM layer */
184         whd_sdpcm_process_rx_packet(whd_driver, recv_buffer);
185     }
186     return (int8_t)1;
187 }
188 
189 /** Sends and Receives all waiting packets
190  *
191  * Calls whd_thread_send_one_packet and whd_thread_receive_one_packet
192  * once to send and receive packets, until there are no more packets waiting to
193  * be transferred.
194  *
195  * This function is normally used by the WHD Thread, but can be
196  * called periodically by systems which have no RTOS to ensure
197  * packets get send and received properly.
198  *
199  * Note: do not loop in here, to avoid overwriting previously rx-ed packets
200  */
whd_thread_poll_all(whd_driver_t whd_driver)201 int8_t whd_thread_poll_all(whd_driver_t whd_driver)
202 {
203     int8_t result = 0;
204     result |= whd_thread_send_one_packet(whd_driver);
205     result |= whd_thread_receive_one_packet(whd_driver);
206     return result;
207 }
208 
209 /** Terminates the WHD Thread
210  *
211  * Sets a flag then wakes the WHD Thread to force it to terminate.
212  *
213  */
whd_thread_quit(whd_driver_t whd_driver)214 void whd_thread_quit(whd_driver_t whd_driver)
215 {
216     whd_thread_info_t *thread_info = &whd_driver->thread_info;
217     whd_result_t result;
218 
219     /* signal main thread and wake it */
220     thread_info->thread_quit_flag = WHD_TRUE;
221     result = cy_rtos_set_semaphore(&thread_info->transceive_semaphore, WHD_FALSE);
222     if (result != WHD_SUCCESS)
223     {
224         WPRINT_WHD_ERROR( ("Error setting semaphore in %s at %d \n", __func__, __LINE__) );
225     }
226 
227     /* Wait for the WHD thread to end */
228     cy_rtos_join_thread(&thread_info->whd_thread);
229     /* Delete the semaphore */
230     /* Ignore return - not much can be done about failure */
231     (void)cy_rtos_deinit_semaphore(&thread_info->transceive_semaphore);
232 }
233 
234 /**
235  * Informs WHD of an interrupt
236  *
237  * This function should be called from the SDIO/SPI interrupt function
238  * and usually indicates newly received data is available.
239  * It wakes the WHD Thread, forcing it to check the send/receive
240  *
241  */
242 /* ignore failure since there is nothing that can be done about it in a ISR */
whd_thread_notify_irq(whd_driver_t whd_driver)243 void whd_thread_notify_irq(whd_driver_t whd_driver)
244 {
245     whd_driver->thread_info.bus_interrupt = WHD_TRUE;
246 
247     /* just wake up the main thread and let it deal with the data */
248     if (whd_driver->thread_info.whd_inited == WHD_TRUE)
249     {
250         (void)cy_rtos_set_semaphore(&whd_driver->thread_info.transceive_semaphore, WHD_TRUE);
251     }
252 }
253 
whd_thread_notify(whd_driver_t whd_driver)254 void whd_thread_notify(whd_driver_t whd_driver)
255 {
256     /* just wake up the main thread and let it deal with the data */
257     if (whd_driver->thread_info.whd_inited == WHD_TRUE)
258     {
259         /* Ignore return - not much can be done about failure */
260         (void)cy_rtos_set_semaphore(&whd_driver->thread_info.transceive_semaphore, WHD_FALSE);
261     }
262 }
263 
264 /******************************************************
265 *             Static Functions
266 ******************************************************/
267 
268 /** The WHD Thread function
269  *
270  *  This is the main loop of the WHD Thread.
271  *  It simply calls @ref whd_thread_poll_all to send/receive all waiting packets, then goes
272  *  to sleep.  The sleep has a 100ms timeout, causing the send/receive queues to be
273  *  checked 10 times per second in case an interrupt is missed.
274  *  Once the quit flag has been set, flags/mutexes are cleaned up, and the function exits.
275  *
276  * @param thread_input  : unused parameter needed to match thread prototype.
277  *
278  */
whd_thread_func(cy_thread_arg_t thread_input)279 static void whd_thread_func(cy_thread_arg_t thread_input)
280 {
281     int8_t rx_status;
282     int8_t tx_status;
283     uint8_t rx_cnt, rx_over_bound = 0;
284     uint8_t bus_fail = 0;
285     uint8_t error_type;
286     uint32_t status;
287 
288     whd_driver_t whd_driver = ( whd_driver_t )thread_input;
289     whd_thread_info_t *thread_info = &whd_driver->thread_info;
290 
291     WPRINT_WHD_DATA_LOG( ("Started whd Thread\n") );
292 
293     /* Interrupts may be enabled inside thread. To make sure none lost set flag now. */
294     thread_info->whd_inited = WHD_TRUE;
295 
296     while (thread_info->thread_quit_flag != WHD_TRUE)
297     {
298         rx_cnt = 0;
299         /* Check if we were woken by interrupt */
300         if ( (thread_info->bus_interrupt == WHD_TRUE) ||
301              (whd_bus_use_status_report_scheme(whd_driver) ) )
302         {
303             thread_info->bus_interrupt = WHD_FALSE;
304 
305             /* Check if the interrupt indicated there is a packet to read */
306             status = whd_bus_packet_available_to_read(whd_driver);
307             if ( ( (status != 0) && (status != WHD_BUS_FAIL) ) || rx_over_bound )
308             {
309                 rx_over_bound = 0;
310                 /* Receive all available packets */
311                 do
312                 {
313                     rx_status = whd_thread_receive_one_packet(whd_driver);
314                     rx_cnt++;
315                 } while (rx_status != 0 && rx_cnt < WHD_THREAD_RX_BOUND);
316                 bus_fail = 0;
317             }
318             else
319             {
320                 if (status == WHD_BUS_FAIL)
321                 {
322                     bus_fail++;
323                 }
324             }
325         }
326 
327         /* Send all queued packets */
328         do
329         {
330             tx_status = whd_thread_send_one_packet(whd_driver);
331         } while (tx_status != 0);
332 
333         if (rx_cnt >= WHD_THREAD_RX_BOUND)
334         {
335             thread_info->bus_interrupt = WHD_TRUE;
336             rx_over_bound = 1;
337             continue;
338         }
339         if (bus_fail > WHD_MAX_BUS_FAIL)
340         {
341             WPRINT_WHD_ERROR( ("%s: Error bus_fail over %d times\n", __FUNCTION__, WHD_MAX_BUS_FAIL) );
342             error_type = WLC_ERR_BUS;
343             whd_set_error_handler_locally(whd_driver, &error_type, NULL, NULL, NULL);
344         }
345 
346         /* Sleep till WLAN do something */
347         whd_bus_wait_for_wlan_event(whd_driver, &thread_info->transceive_semaphore);
348         WPRINT_WHD_DATA_LOG( ("whd Thread: Woke\n") );
349     }
350 
351     /* Set flag before releasing objects */
352     thread_info->whd_inited = WHD_FALSE;
353 
354     /* Reset the quit flag */
355     thread_info->thread_quit_flag = WHD_FALSE;
356 
357     whd_sdpcm_quit(whd_driver);
358 
359     WPRINT_WHD_DATA_LOG( ("Stopped whd Thread\n") );
360 
361     /* Ignore return - not much can be done about failure */
362     (void)cy_rtos_exit_thread();
363 }
364