Newer
Older
mbed-os / targets / TARGET_Cypress / TARGET_PSOC6 / abstraction-rtos / source / cy_worker_thread.c
@Dustin Crossman Dustin Crossman on 4 Jun 2021 8 KB Fix file modes.
/***********************************************************************************************//**
 * \file cy_worker_thread.c
 *
 * \brief
 * Provides implementation for functions that allow creating/deleting worker
 * threads and deferring work to a worker thread.
 ***************************************************************************************************
 * \copyright
 * Copyright 2018-2021 Cypress Semiconductor Corporation
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **************************************************************************************************/

#include <stdlib.h>
#include <string.h>

#include "cy_worker_thread.h"
#include "cy_utils.h"
#include "cyabs_rtos_internal.h"
#if defined(CY_USING_HAL)
#include "cyhal_system.h"

#if defined(__cplusplus)
extern "C"
{
#endif

// Info for dispatching a function call
typedef struct
{
    cy_worker_thread_func_t* work_func;
    void*                    arg;
} cy_worker_dispatch_info_t;


//--------------------------------------------------------------------------------------------------
// cy_worker_thread_func
//
/* Worker Thread to dispatch the events that added to the event queue.
 * It will wait indefinitely for a item to be queued and will terminate
 * when the NULL work function is queued by delete. It will process all
 * events before the terminating event.
 * @param   arg : pointer to @ref cy_worker_thread_info_t
 */
//--------------------------------------------------------------------------------------------------
static void cy_worker_thread_func(cy_thread_arg_t arg)
{
    cy_rslt_t                 result;
    cy_worker_dispatch_info_t dispatch_info;
    cy_worker_thread_info_t*  worker = (cy_worker_thread_info_t*)arg;

    while (1)
    {
        result = cy_rtos_get_queue(&worker->event_queue, &dispatch_info, CY_RTOS_NEVER_TIMEOUT,
                                   false);
        if (result == CY_RSLT_SUCCESS)
        {
            if (dispatch_info.work_func != NULL)
            {
                dispatch_info.work_func(dispatch_info.arg);
            }
            else
            {
                break;
            }
        }
    }
    cy_rtos_exit_thread();
}


//--------------------------------------------------------------------------------------------------
// cy_worker_thread_create
//--------------------------------------------------------------------------------------------------
cy_rslt_t cy_worker_thread_create(cy_worker_thread_info_t* new_worker,
                                  const cy_worker_thread_params_t* params)
{
    // Param check
    CY_ASSERT((params != NULL) && (new_worker != NULL));
    CY_ASSERT((params->stack == NULL) || (params->stack != NULL && params->stack_size != 0));

    // Start with a clean structure
    memset(new_worker, 0, sizeof(cy_worker_thread_info_t));

    cy_rslt_t result = cy_rtos_init_queue(&new_worker->event_queue,
                                          (params->num_entries != 0)
                                          ? params->num_entries
                                          : CY_WORKER_DEFAULT_ENTRIES,
                                          sizeof(cy_worker_dispatch_info_t));
    if (result == CY_RSLT_SUCCESS)
    {
        new_worker->state = CY_WORKER_THREAD_VALID;
        result            = cy_rtos_create_thread(&new_worker->thread,
                                                  cy_worker_thread_func,
                                                  (params->name != NULL)
                                                  ? params->name
                                                  : CY_WORKER_THREAD_DEFAULT_NAME,
                                                  params->stack,
                                                  params->stack_size,
                                                  params->priority,
                                                  (cy_thread_arg_t)new_worker);

        if (result != CY_RSLT_SUCCESS)
        {
            new_worker->state = CY_WORKER_THREAD_INVALID;
            cy_rtos_deinit_queue(&new_worker->event_queue);
        }
    }
    return result;
}


//--------------------------------------------------------------------------------------------------
// cy_worker_thread_delete
//--------------------------------------------------------------------------------------------------
cy_rslt_t cy_worker_thread_delete(cy_worker_thread_info_t* old_worker)
{
    cy_rslt_t result = CY_RSLT_SUCCESS;

    uint32_t state = cyhal_system_critical_section_enter();
    if (old_worker->state != CY_WORKER_THREAD_INVALID)
    {
        // Don't allow terminating while cy_rtos_put_queue is running
        if (old_worker->state == CY_WORKER_THREAD_VALID)
        {
            // A terminating event is queued that will break the while loop
            // Note that this is ok because thread enqueue function will not
            // allow NULL as a valid value for the work function.
            old_worker->state = CY_WORKER_THREAD_TERMINATING;
            cyhal_system_critical_section_exit(state);
            cy_worker_dispatch_info_t dispatch_info = { NULL, NULL };
            bool in_isr = is_in_isr();
            result = cy_rtos_put_queue(&old_worker->event_queue, &dispatch_info, 0, in_isr);
            if (result != CY_RSLT_SUCCESS)
            {
                // Could not enqueue termination task, return to valid state
                state = cyhal_system_critical_section_enter();
                old_worker->state = CY_WORKER_THREAD_VALID;
                cyhal_system_critical_section_exit(state);

                return result;
            }
        }

        if (old_worker->state != CY_WORKER_THREAD_JOIN_COMPLETE)
        {
            cyhal_system_critical_section_exit(state);
            result = cy_rtos_join_thread(&old_worker->thread);
            if (result != CY_RSLT_SUCCESS)
            {
                return result;
            }
            state = cyhal_system_critical_section_enter();
            old_worker->state = CY_WORKER_THREAD_JOIN_COMPLETE;
        }

        if (old_worker->state != CY_WORKER_THREAD_INVALID)
        {
            cyhal_system_critical_section_exit(state);
            result = cy_rtos_deinit_queue(&old_worker->event_queue);
            if (result != CY_RSLT_SUCCESS)
            {
                return result;
            }
            state = cyhal_system_critical_section_enter();
            old_worker->state = CY_WORKER_THREAD_INVALID;
        }
    }

    cyhal_system_critical_section_exit(state);
    return result;
}


//--------------------------------------------------------------------------------------------------
// cy_worker_thread_enqueue
//--------------------------------------------------------------------------------------------------
cy_rslt_t cy_worker_thread_enqueue(cy_worker_thread_info_t* worker_info,
                                   cy_worker_thread_func_t* work_func, void* arg)
{
    CY_ASSERT(worker_info != NULL);
    CY_ASSERT(work_func != NULL);

    uint32_t state = cyhal_system_critical_section_enter();
    if ((worker_info->state != CY_WORKER_THREAD_VALID) &&
        (worker_info->state != CY_WORKER_THREAD_ENQUEUING))
    {
        cyhal_system_critical_section_exit(state);
        return CY_WORKER_THREAD_ERR_THREAD_INVALID;
    }
    worker_info->enqueue_count++;
    worker_info->state = CY_WORKER_THREAD_ENQUEUING;
    cyhal_system_critical_section_exit(state);

    cy_worker_dispatch_info_t dispatch_info = { work_func, arg };
    // Queue an event to be run by the worker thread
    bool in_isr = is_in_isr();
    cy_rslt_t result = cy_rtos_put_queue(&worker_info->event_queue, &dispatch_info, 0, in_isr);

    state = cyhal_system_critical_section_enter();
    worker_info->enqueue_count--;
    if (worker_info->enqueue_count == 0)
    {
        worker_info->state = CY_WORKER_THREAD_VALID;
    }
    cyhal_system_critical_section_exit(state);

    return result;
}


#if defined(__cplusplus)
}
#endif

#endif // defined(CY_USING_HAL)