/***************************************************************************
 * Copyright (c) 2024 Microsoft Corporation
 * Copyright (c) 2026-present Eclipse ThreadX contributors
 *
 * This program and the accompanying materials are made available under the
 * terms of the MIT License which is available at
 * https://opensource.org/licenses/MIT.
 *
 * SPDX-License-Identifier: MIT
 **************************************************************************/

/**************************************************************************/
/**************************************************************************/
/**                                                                       */
/** USBX Component                                                        */
/**                                                                       */
/**   Device CCID Class                                                   */
/**                                                                       */
/**************************************************************************/
/**************************************************************************/

#define UX_SOURCE_CODE


/* Include necessary system files.  */

#include "ux_api.h"
#include "ux_device_class_ccid.h"
#include "ux_device_stack.h"


#if defined(UX_DEVICE_STANDALONE)

static inline UINT _ux_device_class_ccid_cmd_task(UX_DEVICE_CLASS_CCID *ccid);
static inline UINT _ux_device_class_ccid_rsp_task(UX_DEVICE_CLASS_CCID *ccid);


/**************************************************************************/
/*                                                                        */
/*  FUNCTION                                               RELEASE        */
/*                                                                        */
/*    _ux_device_class_ccid_tasks_run                     PORTABLE C      */
/*                                                           6.2.1        */
/*  AUTHOR                                                                */
/*                                                                        */
/*    Chaoqiong Xiao, Microsoft Corporation                               */
/*                                                                        */
/*  DESCRIPTION                                                           */
/*                                                                        */
/*    This function is the background task of the CCID.                   */
/*                                                                        */
/*    It's for standalone mode.                                           */
/*                                                                        */
/*  INPUT                                                                 */
/*                                                                        */
/*    instance                              Pointer to CCID class         */
/*                                                                        */
/*  OUTPUT                                                                */
/*                                                                        */
/*    State machine status                                                */
/*    UX_STATE_EXIT                         Device not configured         */
/*    UX_STATE_IDLE                         No streaming transfer running */
/*    UX_STATE_WAIT                         Streaming transfer running    */
/*                                                                        */
/*  CALLS                                                                 */
/*                                                                        */
/*    ux_device_class_ccid_notify_task_run Run interrupt notify task      */
/*    ux_device_class_ccid_runner_task_run Run slot command runner task   */
/*                                                                        */
/*  CALLED BY                                                             */
/*                                                                        */
/*    USBX Device Stack                                                   */
/*                                                                        */
/**************************************************************************/
UINT _ux_device_class_ccid_tasks_run(VOID *instance)
{
UX_SLAVE_DEVICE                 *device;
UX_DEVICE_CLASS_CCID            *ccid;
ULONG                           run_count = 0;


    /* Get ccid instance.  */
    ccid = (UX_DEVICE_CLASS_CCID *) instance;

    /* Get the pointer to the device.  */
    device =  &_ux_system_slave -> ux_system_slave_device;

    /* Check if the device is configured.  */
    if (device -> ux_slave_device_state != UX_DEVICE_CONFIGURED)
        return(UX_STATE_EXIT);

    /* Bulk OUT command process.  */
    if (_ux_device_class_ccid_cmd_task(ccid) != UX_STATE_IDLE)
        run_count ++;

    /* Bulk IN response process.  */
    if (_ux_device_class_ccid_rsp_task(ccid) != UX_STATE_IDLE)
        run_count ++;

    /* Interrupt IN notification process.  */
    if (_ux_device_class_ccid_notify_task_run(ccid) != UX_STATE_IDLE)
        run_count ++;

    /* Runner tasks process.  */
    if (_ux_device_class_ccid_runner_task_run(ccid) != UX_STATE_IDLE)
        run_count ++;

    return((run_count > 0) ? UX_STATE_WAIT : UX_STATE_IDLE);
}
static inline UINT _ux_device_class_ccid_cmd_task(UX_DEVICE_CLASS_CCID *ccid)
{
UX_INTERRUPT_SAVE_AREA
INT                                                 immediate_state = UX_TRUE;
UINT                                                status;
UX_DEVICE_CLASS_CCID_SLOT                           *slot;
UX_DEVICE_CLASS_CCID_RUNNER                         *runner = UX_NULL;
UX_DEVICE_CLASS_CCID_PARAMETER                      *parameter;
UX_DEVICE_CLASS_CCID_MESSAGES                       messages;
UX_SLAVE_ENDPOINT                                   *endpoint;
UX_SLAVE_TRANSFER                                   *transfer_cmd;
UX_DEVICE_CLASS_CCID_MESSAGE_HEADER                 *cmd;
UX_DEVICE_CLASS_CCID_RDR_TO_PC_SLOT_STATUS_HEADER   *rsp;
UX_DEVICE_CLASS_CCID_COMMAND_SETT                   *cmd_sett;
CHAR                                                cmd_index;
UX_DEVICE_CLASS_CCID_HANDLE                         *handles;


    /* Check endpoint.  */
    endpoint = ccid -> ux_device_class_ccid_endpoint_out;
    if (endpoint == UX_NULL)
    {
        ccid -> ux_device_class_ccid_cmd_state = UX_STATE_RESET;
        return(UX_STATE_EXIT);
    }
    transfer_cmd = &endpoint -> ux_slave_endpoint_transfer_request;

    /* Get running settings.  */
    parameter = &ccid -> ux_device_class_ccid_parameter;

    /* Wait Bulk-OUT command.  */
    while(immediate_state)
    {

        /* Process state.  */
        switch(ccid -> ux_device_class_ccid_cmd_state)
        {
        case UX_DEVICE_CLASS_CCID_CMD_START:

            /* Prepare transfer, next: wait.  */
            ccid -> ux_device_class_ccid_cmd_state = UX_DEVICE_CLASS_CCID_CMD_WAIT;
            UX_SLAVE_TRANSFER_STATE_RESET(transfer_cmd);

            /* Fall through.  */
        case UX_DEVICE_CLASS_CCID_CMD_WAIT:
            status = _ux_device_stack_transfer_run(transfer_cmd,
                    parameter -> ux_device_class_ccid_max_transfer_length,
                    parameter -> ux_device_class_ccid_max_transfer_length);

            /* Error case.  */
            if (status < UX_STATE_NEXT)
            {

                ccid -> ux_device_class_ccid_cmd_state = UX_STATE_RESET;
                return(UX_STATE_ERROR);
            }

            /* Success case.  */
            if (status == UX_STATE_NEXT)
            {

                /* Check transfer results.  */
                if ((transfer_cmd -> ux_slave_transfer_request_completion_code != UX_SUCCESS) ||
                    (transfer_cmd -> ux_slave_transfer_request_actual_length <
                        UX_DEVICE_CLASS_CCID_MESSAGE_HEADER_LENGTH) ||
                    (transfer_cmd -> ux_slave_transfer_request_actual_length >
                        parameter -> ux_device_class_ccid_max_transfer_length))
                {

                    ccid -> ux_device_class_ccid_cmd_state = UX_STATE_RESET;
                    return(UX_STATE_ERROR);
                }

                /* Access to CCID command message header.  */
                cmd = (UX_DEVICE_CLASS_CCID_MESSAGE_HEADER *)
                                transfer_cmd -> ux_slave_transfer_request_data_pointer;

                /* Get command setting index.  */
                cmd_sett = (UX_DEVICE_CLASS_CCID_COMMAND_SETT *)_ux_device_class_ccid_command_sett;
                for (cmd_index = 0; cmd_index < UX_DEVICE_CLASS_CCID_N_COMMANDS;)
                {
                    if (cmd -> bMessageType ==
                        cmd_sett -> ux_device_class_ccid_command_sett_command_type)
                        break;

                    /* Next command setting.  */
                    cmd_sett ++;
                    cmd_index ++;
                }

                /* Save command index for further actions.  */
                ccid -> ux_device_class_ccid_cmd_index = cmd_index;

                /* Next: lock and update status.  */
                ccid -> ux_device_class_ccid_cmd_state = UX_DEVICE_CLASS_CCID_CMD_LOCK;
                continue;
            }

            /* Wait.  */
            return(UX_STATE_WAIT);

        case UX_DEVICE_CLASS_CCID_CMD_LOCK:
            UX_DISABLE
            if (ccid -> ux_device_class_ccid_flags & UX_DEVICE_CLASS_CCID_FLAG_LOCK)
            {
                UX_RESTORE
                return(UX_STATE_WAIT);
            }
            ccid -> ux_device_class_ccid_flags |= UX_DEVICE_CLASS_CCID_FLAG_LOCK;
            UX_RESTORE

            /* Fall through.  */
        case UX_DEVICE_CLASS_CCID_CMD_PROCESS:
            cmd = (UX_DEVICE_CLASS_CCID_MESSAGE_HEADER *)
                            transfer_cmd -> ux_slave_transfer_request_data_pointer;
            cmd_index = ccid -> ux_device_class_ccid_cmd_index;
            cmd_sett = (UX_DEVICE_CLASS_CCID_COMMAND_SETT *)&_ux_device_class_ccid_command_sett[(INT)cmd_index];
            handles = (UX_DEVICE_CLASS_CCID_HANDLE *)parameter -> ux_device_class_ccid_handles;

            /* Initialize response.  */
            rsp = (UX_DEVICE_CLASS_CCID_RDR_TO_PC_SLOT_STATUS_HEADER *)
                                                ccid -> ux_device_class_ccid_header;
            _ux_utility_memory_set(rsp, 0, UX_DEVICE_CLASS_CCID_MESSAGE_HEADER_LENGTH); /* Use case of memset is verified. */
            rsp -> bMessageType = cmd_sett -> ux_device_class_ccid_command_sett_response_type;
            rsp -> bSlot        = cmd -> bSlot;
            rsp -> bSeq         = cmd -> bSeq;

            /* Check command support (0,1,0).  */
            if (rsp -> bMessageType == 0 ||
                handles[(INT)cmd_sett -> ux_device_class_ccid_command_sett_handle_index] == UX_NULL)
            {

                /* Response: command not supported (0,1,0).  */
                rsp -> bStatus = UX_DEVICE_CLASS_CCID_SLOT_STATUS(0, 1);
                _ux_device_class_ccid_unlock(ccid);

                /* Next: response.  */
                ccid -> ux_device_class_ccid_cmd_state = UX_DEVICE_CLASS_CCID_CMD_RSP_START;
                return(UX_STATE_IDLE);
            }

            /* check Slot exist (2,1,5).  */
            if (cmd -> bSlot >= parameter -> ux_device_class_ccid_max_n_slots)
            {

                /* Response: Slot not exist.  */
                rsp -> bStatus = UX_DEVICE_CLASS_CCID_SLOT_STATUS(2, 1);
                rsp -> bError  = 5;
                _ux_device_class_ccid_unlock(ccid);

                /* Next: response.  */
                ccid -> ux_device_class_ccid_cmd_state = UX_DEVICE_CLASS_CCID_CMD_RSP_START;
                return(UX_STATE_IDLE);
            }

            /* Get slot instance for later usage (optimized only 1 slot).  */
            slot = ccid -> ux_device_class_ccid_slots;

            /* Initialize response status from slot status.  */
            rsp -> bStatus = UX_DEVICE_CLASS_CCID_SLOT_STATUS(
                                slot -> ux_device_class_ccid_slot_icc_status, 0);

            /* Initialize response clock status.  */
            if (cmd_sett -> ux_device_class_ccid_command_sett_response_type == 0x81)
                rsp -> bClockStatus = slot -> ux_device_class_ccid_slot_clock_status;

            /* Abort command
                - return slot status(OK) anyway
                - clear aborting status
               Aborting
                - return slot status(ABORTED)
               Other command (except SetDataRateAndClockFrequency)
                - Check busy  */

            /* Abort command is handled differently.  */
            if (cmd -> bMessageType != UX_DEVICE_CLASS_CCID_PC_TO_RDR_ABORT ||
                !slot -> ux_device_class_ccid_slot_aborting)
            {

                /* Check if slot is idle (optimized one slot).  */
                runner = ccid -> ux_device_class_ccid_runners;
                if (ccid -> ux_device_class_ccid_n_busy == 0)
                {

                    /* It's not possible no runner found here, just execute runner.  */

                    /* Runner is busy now.  */
                    runner -> ux_device_class_ccid_runner_slot = (CHAR)cmd->bSlot;
                    runner -> ux_device_class_ccid_runner_command_index = cmd_index;
                    ccid -> ux_device_class_ccid_n_busy ++;

                    /* Create a copy of command and response header.  */
                    _ux_utility_memory_copy(runner -> ux_device_class_ccid_runner_command,
                                            cmd,
                                            transfer_cmd -> ux_slave_transfer_request_actual_length); /* Use case of memcpy is verified. */
                    _ux_utility_memory_copy(runner -> ux_device_class_ccid_runner_response,
                                            rsp, UX_DEVICE_CLASS_CCID_MESSAGE_HEADER_LENGTH); /* Use case of memcpy is verified. */

                    /* Pre-process of command done.  */
                    _ux_device_class_ccid_unlock(ccid);

                    /* Update runner state to start.  */
                    runner -> ux_device_class_ccid_runner_state = UX_DEVICE_CLASS_CCID_RUNNER_START;

                    /* Command processed, command is allowed when runner is executing.  */
                    ccid -> ux_device_class_ccid_cmd_state = UX_DEVICE_CLASS_CCID_CMD_START;
                    return(UX_STATE_WAIT);
                }

                /* Response: Slot Status(busy), optimized for 1 slot.  */
                rsp -> bStatus = UX_DEVICE_CLASS_CCID_SLOT_STATUS(
                                slot -> ux_device_class_ccid_slot_icc_status, 1);
                rsp -> bError = UX_DEVICE_CLASS_CCID_CMD_SLOT_BUSY;
                _ux_device_class_ccid_unlock(ccid);

                /* Next: response (status busy).  */
                ccid -> ux_device_class_ccid_cmd_state = UX_DEVICE_CLASS_CCID_CMD_RSP_START;
                continue;
            }

            /* We are here when we see Abort command, or aborting.
                - Abort command : slot status (ok/fail)
                - Aborting : slot status (CMD_ABORTED)
            */

            /* Abort command.  */
            if (cmd -> bMessageType == UX_DEVICE_CLASS_CCID_PC_TO_RDR_ABORT)
            {

                /* Check sequence.  */
                if (cmd -> bSeq != slot -> ux_device_class_ccid_slot_aborting_seq)
                {

                    /* Response: sequence error.  */
                    rsp -> bStatus = UX_DEVICE_CLASS_CCID_SLOT_STATUS(
                                    slot -> ux_device_class_ccid_slot_icc_status, 1);
                    rsp -> bError  = 6;
                }
                else
                {

                    /* Aborting.  */
                    if (slot -> ux_device_class_ccid_slot_aborting)
                    {

                        /* Call abort handle.  */
                        messages.ux_device_class_ccid_messages_pc_to_rdr = (VOID *)cmd;
                        messages.ux_device_class_ccid_messages_rdr_to_pc = (VOID *)rsp;
                        messages.ux_device_class_ccid_messages_rdr_to_pc_length = 0;
                        parameter -> ux_device_class_ccid_handles ->
                                ux_device_class_ccid_handles_abort(cmd -> bSlot, &messages);

                        /* Status(OK)  */
                        rsp -> bStatus = UX_DEVICE_CLASS_CCID_SLOT_STATUS(
                                        slot -> ux_device_class_ccid_slot_icc_status, 0);

                        /* Free runner (optimized only 1 slot).  */
                        runner = ccid -> ux_device_class_ccid_runners;
                        runner -> ux_device_class_ccid_runner_slot = -1;
                        ccid -> ux_device_class_ccid_n_busy --;

                        /* Clear slot busy and aborting.  */
                        slot -> ux_device_class_ccid_slot_runner = -1;
                        slot -> ux_device_class_ccid_slot_aborting = UX_FALSE;
                    }
                    else
                    {

                        /* Status(CMD_NOT_ABORTED)?  */
                        rsp -> bStatus = UX_DEVICE_CLASS_CCID_SLOT_STATUS(
                                        slot -> ux_device_class_ccid_slot_icc_status, 1);
                        rsp -> bError  = UX_DEVICE_CLASS_CCID_CMD_SLOT_BUSY;
                    }
                }

                _ux_device_class_ccid_unlock(ccid);

                /* Next: response.  */
                ccid -> ux_device_class_ccid_cmd_state = UX_DEVICE_CLASS_CCID_CMD_RSP_START;
                return(UX_STATE_IDLE);
            }

            /* Aborting.  */

            /* Response: Slot Status(aborted).  */
            rsp -> bStatus = UX_DEVICE_CLASS_CCID_SLOT_STATUS(
                                slot -> ux_device_class_ccid_slot_icc_status, 1);
            rsp -> bError = UX_DEVICE_CLASS_CCID_CMD_ABORTED;

            _ux_device_class_ccid_unlock(ccid);

            /* Next: response.  */
            ccid -> ux_device_class_ccid_cmd_state = UX_DEVICE_CLASS_CCID_CMD_RSP_START;

            /* Fall through.  */
        case UX_DEVICE_CLASS_CCID_CMD_RSP_START:

            /* Wait until rsponse task is idle.  */
            if (ccid -> ux_device_class_ccid_rsp_state != UX_DEVICE_CLASS_CCID_RSP_IDLE)
                return(UX_STATE_WAIT);

            /* Start response.  */
            _ux_device_class_ccid_response(ccid,
                                    ccid -> ux_device_class_ccid_header,
                                    UX_DEVICE_CLASS_CCID_MESSAGE_HEADER_LENGTH);

            /* Command is idle and started after response sent.  */
            ccid -> ux_device_class_ccid_flags |= UX_DEVICE_CLASS_CCID_FLAG_CMD_RSP;
            ccid -> ux_device_class_ccid_cmd_state = UX_DEVICE_CLASS_CCID_CMD_IDLE;

            /* Fall through.  */
        case UX_DEVICE_CLASS_CCID_CMD_IDLE:
            return(UX_STATE_IDLE);

        default:
            break;
        }

        /* Break the loop.  */
        immediate_state = UX_FALSE;
    }

    return(UX_STATE_WAIT);
}
static inline UINT _ux_device_class_ccid_rsp_task(UX_DEVICE_CLASS_CCID *ccid)
{
UX_INTERRUPT_SAVE_AREA
INT                                                 immediate_state = UX_TRUE;
UINT                                                status;
UX_SLAVE_ENDPOINT                                   *endpoint;
UX_SLAVE_TRANSFER                                   *transfer_rsp;
ULONG                                               length;

    /* Check endpoint.  */
    endpoint = ccid -> ux_device_class_ccid_endpoint_in;
    if (endpoint == UX_NULL)
    {
        ccid -> ux_device_class_ccid_cmd_state = UX_STATE_RESET;
        return(UX_STATE_EXIT);
    }
    transfer_rsp = &endpoint -> ux_slave_endpoint_transfer_request;

    while(immediate_state)
    {

        switch(ccid -> ux_device_class_ccid_rsp_state)
        {

        case UX_DEVICE_CLASS_CCID_RSP_IDLE:
            return(UX_STATE_IDLE);

        case UX_DEVICE_CLASS_CCID_RSP_START:
            ccid -> ux_device_class_ccid_rsp_state = UX_DEVICE_CLASS_CCID_RSP_WAIT;

            /* Fall through.  */
        case UX_DEVICE_CLASS_CCID_RSP_WAIT:

            /* Run bulk IN transfer.  */
            length = transfer_rsp -> ux_slave_transfer_request_requested_length;
            status = _ux_device_stack_transfer_run(transfer_rsp, length, length);

            /* Error/success case.  */
            if (status <= UX_STATE_NEXT)
            {

                /* Transfer is done.  */

                /* Check if it's command response or runner response.
                   After runner response status needs update (optimized 1 slot).  */
                if (ccid -> ux_device_class_ccid_flags & UX_DEVICE_CLASS_CCID_FLAG_CMD_RSP)
                {
                    ccid -> ux_device_class_ccid_flags &= ~UX_DEVICE_CLASS_CCID_FLAG_CMD_RSP;

                    /* CMD -RSP: done.  */
                    ccid -> ux_device_class_ccid_rsp_state = UX_DEVICE_CLASS_CCID_RSP_DONE;
                    continue;
                }

                /* CMD - RUNNER - RSP : Update status.  */
                ccid -> ux_device_class_ccid_rsp_state = UX_DEVICE_CLASS_CCID_RSP_LOCK;
                continue;

            }

            /* Wait transfer.  */
            return(UX_STATE_WAIT);

        case UX_DEVICE_CLASS_CCID_RSP_LOCK:
            UX_DISABLE
            if (ccid -> ux_device_class_ccid_flags & UX_DEVICE_CLASS_CCID_FLAG_LOCK)
            {
                UX_RESTORE
                return(UX_STATE_WAIT);
            }
            ccid -> ux_device_class_ccid_flags |= UX_DEVICE_CLASS_CCID_FLAG_LOCK;
            UX_RESTORE

            /* Fall through.  */
        case UX_DEVICE_CLASS_CCID_RSP_UPDATE:

            /* Free runner and clear busy slot (optimized 1 slot).  */
            if (ccid -> ux_device_class_ccid_n_busy > 0)
                ccid -> ux_device_class_ccid_n_busy --;

            /* Unlock.  */
            ccid -> ux_device_class_ccid_flags &= ~UX_DEVICE_CLASS_CCID_FLAG_LOCK;

            /* Fall through.  */
        case UX_DEVICE_CLASS_CCID_RSP_DONE:

            /* Start command.  */
            if (ccid -> ux_device_class_ccid_cmd_state == UX_DEVICE_CLASS_CCID_CMD_IDLE)
                ccid -> ux_device_class_ccid_cmd_state = UX_DEVICE_CLASS_CCID_CMD_START;

            /* Next: idle.  */
            ccid -> ux_device_class_ccid_rsp_state = UX_DEVICE_CLASS_CCID_RSP_IDLE;
            return(UX_STATE_IDLE);

        default:
            break;
        }

        /* Break the loop.  */
        immediate_state = UX_FALSE;
    }

    /* Unhandled state.  */
    return(UX_STATE_EXIT);
}

#endif