1 /***********************************************************************************************//**
2  * \file cy_worker_thread.c
3  *
4  * \brief
5  * Provides implementation for functions that allow creating/deleting worker
6  * threads and deferring work to a worker thread.
7  ***************************************************************************************************
8  * \copyright
9  * Copyright 2018-2021 Cypress Semiconductor Corporation (an Infineon company) or
10  * an affiliate of Cypress Semiconductor Corporation
11  *
12  * SPDX-License-Identifier: Apache-2.0
13  *
14  * Licensed under the Apache License, Version 2.0 (the "License");
15  * you may not use this file except in compliance with the License.
16  * You may obtain a copy of the License at
17  *
18  *     http://www.apache.org/licenses/LICENSE-2.0
19  *
20  * Unless required by applicable law or agreed to in writing, software
21  * distributed under the License is distributed on an "AS IS" BASIS,
22  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23  * See the License for the specific language governing permissions and
24  * limitations under the License.
25  **************************************************************************************************/
26 
27 #include <stdlib.h>
28 #include <string.h>
29 
30 #include "cy_worker_thread.h"
31 #include "cy_utils.h"
32 #include "cyabs_rtos_internal.h"
33 #if defined(CY_USING_HAL)
34 #include "cyhal_system.h"
35 
36 #if defined(__cplusplus)
37 extern "C"
38 {
39 #endif
40 
41 // Info for dispatching a function call
42 typedef struct
43 {
44     cy_worker_thread_func_t* work_func;
45     void*                    arg;
46 } cy_worker_dispatch_info_t;
47 
48 
49 //--------------------------------------------------------------------------------------------------
50 // cy_worker_thread_func
51 //
52 /* Worker Thread to dispatch the events that added to the event queue.
53  * It will wait indefinitely for a item to be queued and will terminate
54  * when the NULL work function is queued by delete. It will process all
55  * events before the terminating event.
56  * @param   arg : pointer to @ref cy_worker_thread_info_t
57  */
58 //--------------------------------------------------------------------------------------------------
cy_worker_thread_func(cy_thread_arg_t arg)59 static void cy_worker_thread_func(cy_thread_arg_t arg)
60 {
61     cy_rslt_t                 result;
62     cy_worker_dispatch_info_t dispatch_info;
63     cy_worker_thread_info_t*  worker = (cy_worker_thread_info_t*)arg;
64 
65     while (1)
66     {
67         result = cy_rtos_get_queue(&worker->event_queue, &dispatch_info, CY_RTOS_NEVER_TIMEOUT,
68                                    false);
69         if (result == CY_RSLT_SUCCESS)
70         {
71             if (dispatch_info.work_func != NULL)
72             {
73                 dispatch_info.work_func(dispatch_info.arg);
74             }
75             else
76             {
77                 break;
78             }
79         }
80     }
81     cy_rtos_exit_thread();
82 }
83 
84 
85 //--------------------------------------------------------------------------------------------------
86 // cy_worker_thread_create
87 //--------------------------------------------------------------------------------------------------
cy_worker_thread_create(cy_worker_thread_info_t * new_worker,const cy_worker_thread_params_t * params)88 cy_rslt_t cy_worker_thread_create(cy_worker_thread_info_t* new_worker,
89                                   const cy_worker_thread_params_t* params)
90 {
91     // Param check
92     CY_ASSERT((params != NULL) && (new_worker != NULL));
93     CY_ASSERT((params->stack == NULL) || ((params->stack != NULL) && (params->stack_size != 0)));
94 
95     // Start with a clean structure
96     memset(new_worker, 0, sizeof(cy_worker_thread_info_t));
97 
98     cy_rslt_t result = cy_rtos_init_queue(&new_worker->event_queue,
99                                           (params->num_entries != 0)
100                                           ? params->num_entries
101                                           : CY_WORKER_DEFAULT_ENTRIES,
102                                           sizeof(cy_worker_dispatch_info_t));
103     if (result == CY_RSLT_SUCCESS)
104     {
105         new_worker->state = CY_WORKER_THREAD_VALID;
106         result            = cy_rtos_create_thread(&new_worker->thread,
107                                                   cy_worker_thread_func,
108                                                   (params->name != NULL)
109                                                   ? params->name
110                                                   : CY_WORKER_THREAD_DEFAULT_NAME,
111                                                   params->stack,
112                                                   params->stack_size,
113                                                   params->priority,
114                                                   (cy_thread_arg_t)new_worker);
115 
116         if (result != CY_RSLT_SUCCESS)
117         {
118             new_worker->state = CY_WORKER_THREAD_INVALID;
119             cy_rtos_deinit_queue(&new_worker->event_queue);
120         }
121     }
122     return result;
123 }
124 
125 
126 //--------------------------------------------------------------------------------------------------
127 // cy_worker_thread_delete
128 //--------------------------------------------------------------------------------------------------
cy_worker_thread_delete(cy_worker_thread_info_t * old_worker)129 cy_rslt_t cy_worker_thread_delete(cy_worker_thread_info_t* old_worker)
130 {
131     cy_rslt_t result = CY_RSLT_SUCCESS;
132 
133     uint32_t state = cyhal_system_critical_section_enter();
134     if (old_worker->state != CY_WORKER_THREAD_INVALID)
135     {
136         // Don't allow terminating while cy_rtos_put_queue is running
137         if (old_worker->state == CY_WORKER_THREAD_VALID)
138         {
139             // A terminating event is queued that will break the while loop
140             // Note that this is ok because thread enqueue function will not
141             // allow NULL as a valid value for the work function.
142             old_worker->state = CY_WORKER_THREAD_TERMINATING;
143             cyhal_system_critical_section_exit(state);
144             cy_worker_dispatch_info_t dispatch_info = { NULL, NULL };
145             bool in_isr = is_in_isr();
146             result = cy_rtos_put_queue(&old_worker->event_queue, &dispatch_info, 0, in_isr);
147             if (result != CY_RSLT_SUCCESS)
148             {
149                 // Could not enqueue termination task, return to valid state
150                 state = cyhal_system_critical_section_enter();
151                 old_worker->state = CY_WORKER_THREAD_VALID;
152                 cyhal_system_critical_section_exit(state);
153 
154                 return result;
155             }
156         }
157 
158         if (old_worker->state != CY_WORKER_THREAD_JOIN_COMPLETE)
159         {
160             cyhal_system_critical_section_exit(state);
161             result = cy_rtos_join_thread(&old_worker->thread);
162             if (result != CY_RSLT_SUCCESS)
163             {
164                 return result;
165             }
166             state = cyhal_system_critical_section_enter();
167             old_worker->state = CY_WORKER_THREAD_JOIN_COMPLETE;
168         }
169 
170         if (old_worker->state != CY_WORKER_THREAD_INVALID)
171         {
172             cyhal_system_critical_section_exit(state);
173             result = cy_rtos_deinit_queue(&old_worker->event_queue);
174             if (result != CY_RSLT_SUCCESS)
175             {
176                 return result;
177             }
178             state = cyhal_system_critical_section_enter();
179             old_worker->state = CY_WORKER_THREAD_INVALID;
180         }
181     }
182 
183     cyhal_system_critical_section_exit(state);
184     return result;
185 }
186 
187 
188 //--------------------------------------------------------------------------------------------------
189 // cy_worker_thread_enqueue
190 //--------------------------------------------------------------------------------------------------
cy_worker_thread_enqueue(cy_worker_thread_info_t * worker_info,cy_worker_thread_func_t * work_func,void * arg)191 cy_rslt_t cy_worker_thread_enqueue(cy_worker_thread_info_t* worker_info,
192                                    cy_worker_thread_func_t* work_func, void* arg)
193 {
194     CY_ASSERT(worker_info != NULL);
195     CY_ASSERT(work_func != NULL);
196 
197     uint32_t state = cyhal_system_critical_section_enter();
198     if ((worker_info->state != CY_WORKER_THREAD_VALID) &&
199         (worker_info->state != CY_WORKER_THREAD_ENQUEUING))
200     {
201         cyhal_system_critical_section_exit(state);
202         return CY_WORKER_THREAD_ERR_THREAD_INVALID;
203     }
204     worker_info->enqueue_count++;
205     worker_info->state = CY_WORKER_THREAD_ENQUEUING;
206     cyhal_system_critical_section_exit(state);
207 
208     cy_worker_dispatch_info_t dispatch_info = { work_func, arg };
209     // Queue an event to be run by the worker thread
210     bool in_isr = is_in_isr();
211     cy_rslt_t result = cy_rtos_put_queue(&worker_info->event_queue, &dispatch_info, 0, in_isr);
212 
213     state = cyhal_system_critical_section_enter();
214     worker_info->enqueue_count--;
215     if (worker_info->enqueue_count == 0)
216     {
217         worker_info->state = CY_WORKER_THREAD_VALID;
218     }
219     cyhal_system_critical_section_exit(state);
220 
221     return result;
222 }
223 
224 
225 #if defined(__cplusplus)
226 }
227 #endif
228 
229 #endif // defined(CY_USING_HAL)
230