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