UcOs-III 原始碼閱讀: os_q.c

炽杨發表於2024-10-31
//作用:包含用於管理訊息佇列的程式碼
/*
*********************************************************************************************************
*                                              uC/OS-III
*                                        The Real-Time Kernel
*
*                    Copyright 2009-2022 Silicon Laboratories Inc. www.silabs.com
*
*                                 SPDX-License-Identifier: APACHE-2.0
*
*               This software is subject to an open source license and is distributed by
*                Silicon Laboratories Inc. pursuant to the terms of the Apache License,
*                    Version 2.0 available at www.apache.org/licenses/LICENSE-2.0.
*
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                       訊息佇列管理
*
* 檔案    : os_q.c
* 版本    : V3.08.02
*********************************************************************************************************
*/

#define  MICRIUM_SOURCE
#include "os.h"

#ifdef VSC_INCLUDE_SOURCE_FILE_NAMES
const  CPU_CHAR  *os_q__c = "$Id: $";
#endif


#if (OS_CFG_Q_EN > 0u)
/*
************************************************************************************************************************
*                                               建立訊息佇列
*
* 描述: 此函式由您的應用程式呼叫以建立一個訊息佇列。訊息佇列必須在使用前建立。
*
* 引數  : p_q         指向訊息佇列的指標
*
*        p_name      是一個指向 ASCII 字串的指標,用於命名訊息佇列
*
*        max_qty     表示訊息佇列的最大大小(必須是非零值)。請注意,它也不能超過可用的 OS_MSG 數量的最大值。
*
*        p_err       是一個指向變數的指標,該變數將包含此函式返回的錯誤程式碼。
*
*                              OS_ERR_NONE                    呼叫成功
*                              OS_ERR_CREATE_ISR              不能從 ISR 中建立
*                              OS_ERR_ILLEGAL_CREATE_RUN_TIME 如果在呼叫 OSSafetyCriticalStart() 之後嘗試建立佇列
*                              OS_ERR_OBJ_PTR_NULL            如果傳遞了 'p_q' 的空指標
*                              OS_ERR_Q_SIZE                  如果指定的大小為 0
*                              OS_ERR_OBJ_CREATED             如果訊息佇列已建立
*
* 返回: 無
*
* 注意: 無
************************************************************************************************************************
*/

/**
 * @brief 建立一個訊息佇列
 *
 * @param p_q 指向訊息佇列結構的指標
 * @param p_name 佇列的名稱
 * @param max_qty 佇列的最大訊息數量
 * @param p_err 指向錯誤程式碼的指標
 */
void OSQCreate(OS_Q *p_q, CPU_CHAR *p_name, OS_MSG_QTY max_qty, OS_ERR *p_err)
{
    CPU_SR_ALLOC(); // 分配CPU狀態暫存器變數

    // 安全關鍵系統檢查:確保錯誤引數不為空
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

    // 安全關鍵系統檢查:禁止在執行時建立
#ifdef OS_SAFETY_CRITICAL_IEC61508
    if (OSSafetyCriticalStartFlag == OS_TRUE) {
        *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME;
        return;
    }
#endif

    // 檢查是否在中斷服務程式中呼叫
#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {
        *p_err = OS_ERR_CREATE_ISR;
        return;
    }
#endif

    // 引數有效性檢查
#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_q == (OS_Q *)0) {
        *p_err = OS_ERR_OBJ_PTR_NULL;
        return;
    }
    if (max_qty == 0u) {
        *p_err = OS_ERR_Q_SIZE;
        return;
    }
#endif

    CPU_CRITICAL_ENTER(); // 進入臨界區

#if (OS_OBJ_TYPE_REQ > 0u)
#if (OS_CFG_OBJ_CREATED_CHK_EN > 0u)
    if (p_q->Type == OS_OBJ_TYPE_Q) {
        CPU_CRITICAL_EXIT();
        *p_err = OS_ERR_OBJ_CREATED;
        return;
    }
#endif
    p_q->Type = OS_OBJ_TYPE_Q; // 標記資料結構為訊息佇列
#endif

#if (OS_CFG_DBG_EN > 0u)
    p_q->NamePtr = p_name; // 設定佇列名稱
#else
    (void)p_name; // 忽略名稱引數
#endif

    OS_MsgQInit(&p_q->MsgQ, max_qty); // 初始化佇列
    OS_PendListInit(&p_q->PendList); // 初始化等待列表

#if (OS_CFG_DBG_EN > 0u)
    OS_QDbgListAdd(p_q); // 將佇列新增到除錯列表
    OSQQty++; // 佇列數量加一
#endif

    OS_TRACE_Q_CREATE(p_q, p_name); // 記錄佇列建立事件
    CPU_CRITICAL_EXIT(); // 退出臨界區
    *p_err = OS_ERR_NONE; // 設定錯誤程式碼為無錯誤
}


/*
************************************************************************************************************************
*                                               刪除訊息佇列
*
* 描述: 此函式刪除一個訊息佇列,並準備好所有在該佇列上等待的任務。
*
* 引數  : p_q       是指向要刪除的訊息佇列的指標
*
*              opt       確定刪除選項,如下所示:
*
*                            OS_OPT_DEL_NO_PEND          僅當沒有任務等待時刪除佇列
*                            OS_OPT_DEL_ALWAYS           即使有任務在等待,也刪除佇列。
*                                                        在這種情況下,所有等待的任務將被準備好。
*
*              p_err     是一個指向變數的指標,該變數將包含此函式返回的錯誤程式碼。
*
*                            OS_ERR_NONE                    呼叫成功且佇列已刪除
*                            OS_ERR_DEL_ISR                 如果嘗試從ISR中刪除佇列
*                            OS_ERR_ILLEGAL_DEL_RUN_TIME    如果在呼叫OSStart()之後嘗試刪除訊息佇列
*                            OS_ERR_OBJ_PTR_NULL            如果傳遞的'p_q'指標為NULL
*                            OS_ERR_OBJ_TYPE                如果訊息佇列未建立
*                            OS_ERR_OPT_INVALID             指定了無效的選項
*                            OS_ERR_OS_NOT_RUNNING          如果uC/OS-III尚未執行
*                            OS_ERR_TASK_WAITING            一個或多個任務正在等待佇列
*
* 返回值   : == 0          如果沒有任務在等待佇列,或者發生錯誤。
*              >  0          如果一個或多個等待佇列的任務現在已被準備好並通知。
*
* 注意     : 1) 使用此函式必須小心。通常期望佇列存在的任務必須檢查OSQPend()的返回程式碼。
*
*              2) 因為所有在佇列上等待的任務都將被準備好,所以在應用程式中使用佇列進行互斥時必須小心,
*                 因為資源將不再受佇列保護。
************************************************************************************************************************
*/

#if (OS_CFG_Q_DEL_EN > 0u)
OS_OBJ_QTY OSQDel(OS_Q *p_q, OS_OPT opt, OS_ERR *p_err)
{
    OS_OBJ_QTY     nbr_tasks;
    OS_PEND_LIST  *p_pend_list;
    OS_TCB        *p_tcb;
    CPU_TS         ts;
    CPU_SR_ALLOC();

    // 安全關鍵系統檢查:確保錯誤引數不為空
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (0u);
    }
#endif

    // 記錄佇列刪除進入事件
    OS_TRACE_Q_DEL_ENTER(p_q, opt);

    // 安全關鍵系統檢查:禁止在執行時刪除
#ifdef OS_SAFETY_CRITICAL_IEC61508
    if (OSSafetyCriticalStartFlag == OS_TRUE) {
        OS_TRACE_Q_DEL_EXIT(OS_ERR_ILLEGAL_DEL_RUN_TIME);
        *p_err = OS_ERR_ILLEGAL_DEL_RUN_TIME;
        return (0u);
    }
#endif

    // 檢查是否在中斷服務程式中呼叫
#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {                                 /* 不能從ISR中刪除訊息佇列 */
        OS_TRACE_Q_DEL_EXIT(OS_ERR_DEL_ISR);
        *p_err = OS_ERR_DEL_ISR;
        return (0u);
    }
#endif

    // 檢查核心是否正在執行
#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING) {                     /* 核心是否正在執行? */
        OS_TRACE_Q_DEL_EXIT(OS_ERR_OS_NOT_RUNNING);
        *p_err = OS_ERR_OS_NOT_RUNNING;
        return (0u);
    }
#endif

    // 引數有效性檢查
#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_q == (OS_Q *)0) {                                     /* 驗證 'p_q' */
        OS_TRACE_Q_DEL_EXIT(OS_ERR_OBJ_PTR_NULL);
        *p_err =  OS_ERR_OBJ_PTR_NULL;
        return (0u);
    }
#endif

    // 物件型別檢查
#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_q->Type != OS_OBJ_TYPE_Q) {                           /* 確保訊息佇列已建立 */
        OS_TRACE_Q_DEL_EXIT(OS_ERR_OBJ_TYPE);
        *p_err = OS_ERR_OBJ_TYPE;
        return (0u);
    }
#endif

    CPU_CRITICAL_ENTER(); // 進入臨界區
    p_pend_list = &p_q->PendList;
    nbr_tasks   = 0u;

    switch (opt) {
        case OS_OPT_DEL_NO_PEND:                                /* 僅當沒有任務等待時刪除訊息佇列 */
            if (p_pend_list->HeadPtr == (OS_TCB *)0) {
#if (OS_CFG_DBG_EN > 0u)
                OS_QDbgListRemove(p_q);
                OSQQty--;
#endif
                OS_TRACE_Q_DEL(p_q);
                OS_QClr(p_q);
                CPU_CRITICAL_EXIT();
                *p_err = OS_ERR_NONE;
            } else {
                CPU_CRITICAL_EXIT();
                *p_err = OS_ERR_TASK_WAITING;
            }
            break;

        case OS_OPT_DEL_ALWAYS:                                 /* 始終刪除訊息佇列 */
#if (OS_CFG_TS_EN > 0u)
            ts = OS_TS_GET();                                  /* 獲取本地時間戳,以便所有任務獲得相同的時間 */
#else
            ts = 0u;
#endif
            while (p_pend_list->HeadPtr != (OS_TCB *)0) {      /* 從等待列表中移除所有任務 */
                p_tcb = p_pend_list->HeadPtr;
                OS_PendAbort(p_tcb,
                             ts,
                             OS_STATUS_PEND_DEL);
                nbr_tasks++;
            }
#if (OS_CFG_DBG_EN > 0u)
            OS_QDbgListRemove(p_q);
            OSQQty--;
#endif
            OS_TRACE_Q_DEL(p_q);
            OS_QClr(p_q);
            CPU_CRITICAL_EXIT();
            OSSched();                                         /* 查詢最高優先順序的可執行任務 */
            *p_err = OS_ERR_NONE;
            break;

        default:
            CPU_CRITICAL_EXIT();
            *p_err = OS_ERR_OPT_INVALID;
            break;
    }

    OS_TRACE_Q_DEL_EXIT(*p_err);
    return (nbr_tasks);
}
#endif


/*
************************************************************************************************************************
*                                                     清空佇列
*
* 描述: 此函式用於清空訊息佇列的內容。
*
* 引數   : p_q        是指向要清空的訊息佇列的指標
*
*               p_err      是一個指向變數的指標,該變數將包含此函式返回的錯誤程式碼。
*                              - OS_ERR_NONE: 成功
*                              - OS_ERR_FLUSH_ISR: 如果從ISR中呼叫了此函式
*                              - OS_ERR_OBJ_PTR_NULL: 如果傳遞的'p_q'指標為NULL
*                              - OS_ERR_OBJ_TYPE: 如果訊息佇列未建立
*                              - OS_ERR_OS_NOT_RUNNING: 如果uC/OS-III尚未執行
*
* 返回值   : == 0       如果沒有條目被釋放,或者發生錯誤。
*               >  0       被釋放的條目數量。
*
* 注意     : 1) 使用此函式時應非常小心,因為在清空佇列時,你會失去佇列條目所指向的引用,從而可能導致“記憶體洩漏”。換句話說,佇列條目引用的資料應該被釋放(即,解除分配)。
************************************************************************************************************************
*/

#if (OS_CFG_Q_FLUSH_EN > 0u)
// 函式OSQFlush用於清空一個訊息佇列中的所有訊息
// 引數p_q指向要清空的訊息佇列,p_err用於返回錯誤程式碼
// 返回值為清空的訊息數量
OS_MSG_QTY  OSQFlush (OS_Q    *p_q,
                      OS_ERR  *p_err)
{
    OS_MSG_QTY  entries;
    CPU_SR_ALLOC(); // 分配CPU狀態暫存器,用於臨界段處理



    // 安全關鍵選項:檢查錯誤引數是否為空
    #ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (0u);
    }
    #endif

    // 檢查是否在中斷服務例程中呼叫:不允許在ISR中清空訊息佇列
    #if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {
       *p_err = OS_ERR_FLUSH_ISR;
        return (0u);
    }
    #endif

    // 檢查核心是否正在執行
    #if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING) {
       *p_err = OS_ERR_OS_NOT_RUNNING;
        return (0u);
    }
    #endif

    // 引數檢查:確保訊息佇列指標不為空
    #if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_q == (OS_Q *)0) {
       *p_err = OS_ERR_OBJ_PTR_NULL;
        return (0u);
    }
    #endif

    // 檢查訊息佇列型別是否正確
    #if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_q->Type != OS_OBJ_TYPE_Q) {
       *p_err = OS_ERR_OBJ_TYPE;
        return (0u);
    }
    #endif

    // 臨界段開始:保護訊息佇列清空操作
    CPU_CRITICAL_ENTER();
    entries = OS_MsgQFreeAll(&p_q->MsgQ);                       // 將所有訊息返回到OS_MSG池
    CPU_CRITICAL_EXIT();                                        // 臨界段結束
   *p_err   = OS_ERR_NONE;
    return (entries);
}
#endif


/*
************************************************************************************************************************
*                                            在訊息佇列上等待訊息
*
* 描述: 此函式等待訊息佇列中的訊息。
*
* 引數: p_q           是指向訊息佇列的指標
*
*       timeout       是一個可選的超時時間(以時鐘滴答為單位)。如果非零,任務將等待訊息到達佇列,最長等待時間為該引數指定的時間。如果指定為0,
*                     則任務將在指定佇列上無限期等待,直到訊息到達。
*
*       opt           確定使用者是否希望在佇列為空時阻塞:
*
*                                OS_OPT_PEND_BLOCKING
*                                OS_OPT_PEND_NON_BLOCKING
*
*       p_msg_size    是指向一個變數的指標,該變數將接收訊息的大小
*
*       p_ts          是指向一個變數的指標,該變數將接收訊息接收到的時間戳、掛起被中止的時間或訊息佇列被刪除的時間。如果你傳遞一個NULL指標(即
*                     (CPU_TS *)0),則不會獲取時間戳。換句話說,傳遞NULL指標是有效的,表示你不需要時間戳。
*
*       p_err         是指向一個變數的指標,該變數將包含此函式返回的錯誤程式碼。
*
*                                OS_ERR_NONE               呼叫成功,任務收到了訊息
*                                OS_ERR_OBJ_DEL            如果 'p_q' 被刪除
*                                OS_ERR_OBJ_PTR_NULL       如果你傳遞了 'p_q' 的NULL指標
*                                OS_ERR_OBJ_TYPE           如果訊息佇列未建立
*                                OS_ERR_OPT_INVALID        你指定了無效的選項
*                                OS_ERR_OS_NOT_RUNNING     如果 uC/OS-III 尚未執行
*                                OS_ERR_PEND_ABORT         掛起被中止
*                                OS_ERR_PEND_ISR           如果你從ISR中呼叫了此函式
*                                OS_ERR_PEND_WOULD_BLOCK   如果你指定了非阻塞但佇列為空
*                                OS_ERR_PTR_INVALID        如果你傳遞了 'p_msg_size' 的NULL指標
*                                OS_ERR_SCHED_LOCKED       排程器被鎖定
*                                OS_ERR_STATUS_INVALID     如果掛起狀態有無效值
*                                OS_ERR_TIMEOUT            在指定的超時時間內沒有收到訊息
*                                                          會導致掛起。
*                                OS_ERR_TICK_DISABLED      如果核心滴答被禁用且指定了超時時間
*
* 返回值: != (void *)0  是指向接收到的訊息的指標
*         == (void *)0  如果你收到了NULL指標訊息,或者
*                       如果沒有收到訊息,或者
*                       如果 'p_q' 是NULL指標,或者
*                       如果你沒有傳遞佇列的指標。
*
* 注意: 此API '不得' 從定時器回撥函式中呼叫。
************************************************************************************************************************
*/

void  *OSQPend (OS_Q         *p_q,
                OS_TICK       timeout,
                OS_OPT        opt,
                OS_MSG_SIZE  *p_msg_size,
                CPU_TS       *p_ts,
                OS_ERR       *p_err)
{
    void  *p_void;
    CPU_SR_ALLOC();


#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return ((void *)0);
    }
#endif

    OS_TRACE_Q_PEND_ENTER(p_q, timeout, opt, p_msg_size, p_ts);

#if (OS_CFG_TICK_EN == 0u)
    if (timeout != 0u) {
       *p_err = OS_ERR_TICK_DISABLED;
        OS_TRACE_Q_PEND_FAILED(p_q);
        OS_TRACE_Q_PEND_EXIT(OS_ERR_TICK_DISABLED);
        return ((void *)0);
    }
#endif

#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {                                 /* Not allowed to call from an ISR                      */
        if ((opt & OS_OPT_PEND_NON_BLOCKING) != OS_OPT_PEND_NON_BLOCKING) {
            OS_TRACE_Q_PEND_FAILED(p_q);
            OS_TRACE_Q_PEND_EXIT(OS_ERR_PEND_ISR);
           *p_err = OS_ERR_PEND_ISR;
            return ((void *)0);
        }
    }
#endif

#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING) {                     /* Is the kernel running?                               */
        OS_TRACE_Q_PEND_EXIT(OS_ERR_OS_NOT_RUNNING);
       *p_err = OS_ERR_OS_NOT_RUNNING;
        return ((void *)0);
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_q == (OS_Q *)0) {                                     /* Validate arguments                                   */
        OS_TRACE_Q_PEND_FAILED(p_q);
        OS_TRACE_Q_PEND_EXIT(OS_ERR_OBJ_PTR_NULL);
       *p_err = OS_ERR_OBJ_PTR_NULL;
        return ((void *)0);
    }
    if (p_msg_size == (OS_MSG_SIZE *)0) {
        OS_TRACE_Q_PEND_FAILED(p_q);
        OS_TRACE_Q_PEND_EXIT(OS_ERR_PTR_INVALID);
       *p_err = OS_ERR_PTR_INVALID;
        return ((void *)0);
    }
    switch (opt) {
        case OS_OPT_PEND_BLOCKING:
        case OS_OPT_PEND_NON_BLOCKING:
             break;

        default:
             OS_TRACE_Q_PEND_FAILED(p_q);
             OS_TRACE_Q_PEND_EXIT(OS_ERR_OPT_INVALID);
            *p_err = OS_ERR_OPT_INVALID;
             return ((void *)0);
    }
#endif

#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_q->Type != OS_OBJ_TYPE_Q) {                           /* Make sure message queue was created                  */
        OS_TRACE_Q_PEND_FAILED(p_q);
        OS_TRACE_Q_PEND_EXIT(OS_ERR_OBJ_TYPE);
       *p_err = OS_ERR_OBJ_TYPE;
        return ((void *)0);
    }
#endif

    if (p_ts != (CPU_TS *)0)
    {
        *p_ts = 0u; // 初始化返回的時間戳
    }

    CPU_CRITICAL_ENTER();           // 進入臨界區以確保原子操作
    p_void = OS_MsgQGet(&p_q->MsgQ, // 嘗試從訊息佇列中獲取訊息
                        p_msg_size,
                        p_ts,
                        p_err);

    if (*p_err == OS_ERR_NONE)
    {
        OS_TRACE_Q_PEND(p_q);              // 跟蹤訊息佇列掛起
        CPU_CRITICAL_EXIT();               // 退出臨界區
        OS_TRACE_Q_PEND_EXIT(OS_ERR_NONE); // 跟蹤訊息佇列掛起退出
        return (p_void);                   // 成功,返回接收到的訊息
    }

    if ((opt & OS_OPT_PEND_NON_BLOCKING) != 0u)
    {                                                  // 呼叫者是否希望在不可用時阻塞?
        CPU_CRITICAL_EXIT();                           // 退出臨界區
        OS_TRACE_Q_PEND_FAILED(p_q);                   // 跟蹤訊息佇列掛起失敗
        OS_TRACE_Q_PEND_EXIT(OS_ERR_PEND_WOULD_BLOCK); // 跟蹤訊息佇列掛起退出
        *p_err = OS_ERR_PEND_WOULD_BLOCK;              // 不會阻塞
        return ((void *)0);                            // 返回 NULL
    }
    else
    {
        if (OSSchedLockNestingCtr > 0u)
        {                                              // 當排程器被鎖定時不能掛起
            CPU_CRITICAL_EXIT();                       // 退出臨界區
            OS_TRACE_Q_PEND_FAILED(p_q);               // 跟蹤訊息佇列掛起失敗
            OS_TRACE_Q_PEND_EXIT(OS_ERR_SCHED_LOCKED); // 跟蹤訊息佇列掛起退出
            *p_err = OS_ERR_SCHED_LOCKED;              // 排程器被鎖定
            return ((void *)0);                        // 返回 NULL
        }
    }

    OS_Pend((OS_PEND_OBJ *)((void *)p_q), // 任務在訊息佇列上掛起
            OSTCBCurPtr,
            OS_TASK_PEND_ON_Q,
            timeout);
    CPU_CRITICAL_EXIT();        // 退出臨界區
    OS_TRACE_Q_PEND_BLOCK(p_q); // 跟蹤訊息佇列掛起阻塞
    OSSched();                  // 查詢下一個最高優先順序的任務

    CPU_CRITICAL_ENTER(); // 進入臨界區以確保原子操作
    switch (OSTCBCurPtr->PendStatus)
    {
    case OS_STATUS_PEND_OK: // 從 TCB 中提取訊息(由 Post 放置)
        p_void = OSTCBCurPtr->MsgPtr;
        *p_msg_size = OSTCBCurPtr->MsgSize;
#if (OS_CFG_TS_EN > 0u)
        if (p_ts != (CPU_TS *)0)
        {
            *p_ts = OSTCBCurPtr->TS;
        }
#endif
        OS_TRACE_Q_PEND(p_q); // 跟蹤訊息佇列掛起
        *p_err = OS_ERR_NONE;
        break;

    case OS_STATUS_PEND_ABORT: // 表示我們已中止
        p_void = (void *)0;
        *p_msg_size = 0u;
#if (OS_CFG_TS_EN > 0u)
        if (p_ts != (CPU_TS *)0)
        {
            *p_ts = OSTCBCurPtr->TS;
        }
#endif
        OS_TRACE_Q_PEND_FAILED(p_q); // 跟蹤訊息佇列掛起失敗
        *p_err = OS_ERR_PEND_ABORT;
        break;

    case OS_STATUS_PEND_TIMEOUT: // 表示我們在超時時間內沒有接收到事件
        p_void = (void *)0;
        *p_msg_size = 0u;
        OS_TRACE_Q_PEND_FAILED(p_q); // 跟蹤訊息佇列掛起失敗
        *p_err = OS_ERR_TIMEOUT;
        break;

    case OS_STATUS_PEND_DEL: // 表示掛起的物件已被刪除
        p_void = (void *)0;
        *p_msg_size = 0u;
#if (OS_CFG_TS_EN > 0u)
        if (p_ts != (CPU_TS *)0)
        {
            *p_ts = OSTCBCurPtr->TS;
        }
#endif
        OS_TRACE_Q_PEND_FAILED(p_q); // 跟蹤訊息佇列掛起失敗
        *p_err = OS_ERR_OBJ_DEL;
        break;

    default:
        p_void = (void *)0;
        *p_msg_size = 0u;
        OS_TRACE_Q_PEND_FAILED(p_q); // 跟蹤訊息佇列掛起失敗
        *p_err = OS_ERR_STATUS_INVALID;
        break;
    }
    CPU_CRITICAL_EXIT();          // 退出臨界區
    OS_TRACE_Q_PEND_EXIT(*p_err); // 跟蹤訊息佇列掛起退出
    return (p_void);              // 返回訊息指標
}


/*
************************************************************************************************************************
*                                             中斷訊息佇列上的等待
*
* 描述: 此函式中斷並準備好當前正在佇列上等待的任何任務。此函式應用於故障中斷佇列上的等待,而不是透過 OSQPost() 正常訊號佇列。
*
* 引數: p_q       是指向訊息佇列的指標
*
*       opt       確定執行的中斷型別:
*
*                            OS_OPT_PEND_ABORT_1          中斷單個任務(最高優先順序任務)在佇列上的等待
*                            OS_OPT_PEND_ABORT_ALL        中斷所有在佇列上等待的任務
*                            OS_OPT_POST_NO_SCHED         不呼叫排程器
*
*       p_err     是一個指向變數的指標,該變數將包含此函式返回的錯誤程式碼。
*
*                            OS_ERR_NONE                  至少有一個在佇列上等待的任務被準備好並被告知等待已中斷;檢查返回值以獲取佇列上等待被中斷的任務數量
*                            OS_ERR_OBJ_PTR_NULL          如果傳遞的 'p_q' 是 NULL 指標
*                            OS_ERR_OBJ_TYPE              如果訊息佇列未建立
*                            OS_ERR_OPT_INVALID           指定了無效的選項
*                            OS_ERR_OS_NOT_RUNNING        如果 uC/OS-III 尚未執行
*                            OS_ERR_PEND_ABORT_ISR        如果此函式是從 ISR 中呼叫的
*                            OS_ERR_PEND_ABORT_NONE       沒有任務在等待
*
* 返回值: == 0      如果沒有任務在佇列上等待,或者發生錯誤。
*              >  0      如果一個或多個在佇列上等待的任務現在已被準備好並被告知。
*
* 注意: 無
************************************************************************************************************************
*/

#if (OS_CFG_Q_PEND_ABORT_EN > 0u)
OS_OBJ_QTY OSQPendAbort(OS_Q *p_q, OS_OPT opt, OS_ERR *p_err)
{
    OS_PEND_LIST *p_pend_list;
    OS_TCB *p_tcb;
    CPU_TS ts;
    OS_OBJ_QTY nbr_tasks;
    CPU_SR_ALLOC();

#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (0u);
    }
#endif

#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {                                 /* 不允許從 ISR 中中斷等待 */
        *p_err = OS_ERR_PEND_ABORT_ISR;
        return (0u);
    }
#endif

#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING) {                     /* 核心是否在執行? */
        *p_err = OS_ERR_OS_NOT_RUNNING;
        return (0u);
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_q == (OS_Q *)0) {                                     /* 驗證 'p_q' */
        *p_err = OS_ERR_OBJ_PTR_NULL;
        return (0u);
    }
    switch (opt) {                                              /* 驗證 'opt' */
        case OS_OPT_PEND_ABORT_1:
        case OS_OPT_PEND_ABORT_ALL:
        case OS_OPT_PEND_ABORT_1 | OS_OPT_POST_NO_SCHED:
        case OS_OPT_PEND_ABORT_ALL | OS_OPT_POST_NO_SCHED:
            break;

        default:
            *p_err = OS_ERR_OPT_INVALID;
            return (0u);
    }
#endif

#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_q->Type != OS_OBJ_TYPE_Q) {                           /* 確保佇列已建立 */
        *p_err = OS_ERR_OBJ_TYPE;
        return (0u);
    }
#endif

    CPU_CRITICAL_ENTER();
    p_pend_list = &p_q->PendList;
    if (p_pend_list->HeadPtr == (OS_TCB *)0) {                  /* 有任何任務在佇列上等待嗎? */
        CPU_CRITICAL_EXIT();                                    /* 沒有 */
        *p_err = OS_ERR_PEND_ABORT_NONE;
        return (0u);
    }

    nbr_tasks = 0u;
#if (OS_CFG_TS_EN > 0u)
    ts = OS_TS_GET();                                           /* 獲取本地時間戳,以便所有任務獲得相同的時間 */
#else
    ts = 0u;
#endif
    while (p_pend_list->HeadPtr != (OS_TCB *)0) {
        p_tcb = p_pend_list->HeadPtr;
        OS_PendAbort(p_tcb, ts, OS_STATUS_PEND_ABORT);
        nbr_tasks++;
        if (opt != OS_OPT_PEND_ABORT_ALL) {                     /* 是否中斷所有在佇列上等待的任務? */
            break;                                              /* 否 */
        }
    }
    CPU_CRITICAL_EXIT();

    if ((opt & OS_OPT_POST_NO_SCHED) == 0u) {
        OSSched();                                              /* 執行排程器 */
    }

    *p_err = OS_ERR_NONE;
    return (nbr_tasks);
}
#endif


/*
************************************************************************************************************************
*                                               向佇列傳送訊息
*
* 描述: 此函式向佇列傳送一條訊息。透過 'opt' 引數,您可以指定訊息是否廣播到所有等待的任務,以及您是將訊息釋出到佇列的前端(LIFO)還是通常釋出到佇列的末尾(FIFO)。
*
* 引數: p_q           是一個指向必須由 OSQCreate() 建立的訊息佇列的指標。
*
*       p_void        是一個指向要傳送的訊息的指標。
*
*       msg_size      指定訊息的大小(以位元組為單位)。
*
*       opt           確定執行的 POST 型別:
*
*                                OS_OPT_POST_ALL          向所有在佇列上等待的任務傳送訊息。此選項可以與 OS_OPT_POST_FIFO 或 OS_OPT_POST_LIFO 之一組合。
*                                OS_OPT_POST_FIFO         將訊息釋出到佇列末尾(FIFO)並喚醒一個等待的任務。
*                                OS_OPT_POST_LIFO         將訊息釋出到佇列前端(LIFO)並喚醒一個等待的任務。
*                                OS_OPT_POST_NO_SCHED     不呼叫排程器。
*
*                            注意: 1) OS_OPT_POST_NO_SCHED 可以與其它選項之一組合(或按位或)。
*                                  2) OS_OPT_POST_ALL 可以與其它選項之一組合(或按位或)。
*                                  3) 可能的選項組合包括:
*
*                                        OS_OPT_POST_FIFO
*                                        OS_OPT_POST_LIFO
*                                        OS_OPT_POST_FIFO + OS_OPT_POST_ALL
*                                        OS_OPT_POST_LIFO + OS_OPT_POST_ALL
*                                        OS_OPT_POST_FIFO + OS_OPT_POST_NO_SCHED
*                                        OS_OPT_POST_LIFO + OS_OPT_POST_NO_SCHED
*                                        OS_OPT_POST_FIFO + OS_OPT_POST_ALL + OS_OPT_POST_NO_SCHED
*                                        OS_OPT_POST_LIFO + OS_OPT_POST_ALL + OS_OPT_POST_NO_SCHED
*
*       p_err         是一個指向變數的指標,該變數將包含此函式返回的錯誤程式碼。
*
*                                OS_ERR_NONE              呼叫成功且訊息已傳送。
*                                OS_ERR_MSG_POOL_EMPTY    如果沒有更多的 OS_MSG 可用於放置訊息。
*                                OS_ERR_OBJ_PTR_NULL      如果 'p_q' 是 NULL 指標。
*                                OS_ERR_OBJ_TYPE          如果訊息佇列未初始化。
*                                OS_ERR_OPT_INVALID       指定了無效的選項。
*                                OS_ERR_OS_NOT_RUNNING    如果 uC/OS-III 尚未執行。
*                                OS_ERR_Q_MAX             如果佇列已滿。
*
* 返回值: 無
*
* 注意: 無
************************************************************************************************************************
*/

void OSQPost(OS_Q *p_q, void *p_void, OS_MSG_SIZE msg_size, OS_OPT opt, OS_ERR *p_err)
{
    OS_OPT post_type;
    OS_PEND_LIST *p_pend_list;
    OS_TCB *p_tcb;
    OS_TCB *p_tcb_next;
    CPU_TS ts;
    CPU_SR_ALLOC();

#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

    OS_TRACE_Q_POST_ENTER(p_q, p_void, msg_size, opt);

#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING) {                     /* 核心是否在執行? */
        OS_TRACE_Q_POST_EXIT(OS_ERR_OS_NOT_RUNNING);
        *p_err = OS_ERR_OS_NOT_RUNNING;
        return;
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_q == (OS_Q *)0) {                                     /* 驗證 'p_q' */
        OS_TRACE_Q_POST_FAILED(p_q);
        OS_TRACE_Q_POST_EXIT(OS_ERR_OBJ_PTR_NULL);
        *p_err = OS_ERR_OBJ_PTR_NULL;
        return;
    }
    switch (opt) {                                              /* 驗證 'opt' */
        case OS_OPT_POST_FIFO:
        case OS_OPT_POST_LIFO:
        case OS_OPT_POST_FIFO | OS_OPT_POST_ALL:
        case OS_OPT_POST_LIFO | OS_OPT_POST_ALL:
        case OS_OPT_POST_FIFO | OS_OPT_POST_NO_SCHED:
        case OS_OPT_POST_LIFO | OS_OPT_POST_NO_SCHED:
        case OS_OPT_POST_FIFO | (OS_OPT)(OS_OPT_POST_ALL | OS_OPT_POST_NO_SCHED):
        case OS_OPT_POST_LIFO | (OS_OPT)(OS_OPT_POST_ALL | OS_OPT_POST_NO_SCHED):
            break;

        default:
            OS_TRACE_Q_POST_FAILED(p_q);
            OS_TRACE_Q_POST_EXIT(OS_ERR_OPT_INVALID);
            *p_err = OS_ERR_OPT_INVALID;
            return;
    }
#endif

#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_q->Type != OS_OBJ_TYPE_Q) {                           /* 確保訊息佇列已建立 */
        OS_TRACE_Q_POST_FAILED(p_q);
        OS_TRACE_Q_POST_EXIT(OS_ERR_OBJ_TYPE);
        *p_err = OS_ERR_OBJ_TYPE;
        return;
    }
#endif

#if (OS_CFG_TS_EN > 0u)
    ts = OS_TS_GET();                                           /* 獲取時間戳 */
#else
    ts = 0u;
#endif

    OS_TRACE_Q_POST(p_q);

    CPU_CRITICAL_ENTER();
    p_pend_list = &p_q->PendList;
    if (p_pend_list->HeadPtr == (OS_TCB *)0) {                  /* 有任何任務在訊息佇列上等待嗎? */
        if ((opt & OS_OPT_POST_LIFO) == 0u) {                   /* 確定我們是 FIFO 還是 LIFO 釋出 */
            post_type = OS_OPT_POST_FIFO;
        } else {
            post_type = OS_OPT_POST_LIFO;
        }
        OS_MsgQPut(&p_q->MsgQ,                                  /* 將訊息放入訊息佇列 */
                   p_void,
                   msg_size,
                   post_type,
                   ts,
                   p_err);
        CPU_CRITICAL_EXIT();
        OS_TRACE_Q_POST_EXIT(*p_err);
        return;
    }

    p_tcb = p_pend_list->HeadPtr;
    while (p_tcb != (OS_TCB *)0) {
        p_tcb_next = p_tcb->PendNextPtr;
        OS_Post((OS_PEND_OBJ *)((void *)p_q),
                p_tcb,
                p_void,
                msg_size,
                ts);
        if ((opt & OS_OPT_POST_ALL) == 0u) {                    /* 是否向所有等待的任務釋出訊息? */
            break;                                              /* 否 */
        }
        p_tcb = p_tcb_next;
    }

    CPU_CRITICAL_EXIT();

    if ((opt & OS_OPT_POST_NO_SCHED) == 0u) {
        OSSched();                                              /* 執行排程器 */
    }

    *p_err = OS_ERR_NONE;
    OS_TRACE_Q_POST_EXIT(*p_err);
}


/*
************************************************************************************************************************
*                                        清空訊息佇列的內容
*
* 描述: 此函式由 OSQDel() 呼叫,用於清空訊息佇列的內容
*
* 引數: p_q      是一個指向要清空的佇列的指標
*              ---
*
* 返回值: 無
*
* 注意: 1) 此函式是 uC/OS-III 的內部函式,您的應用程式不得呼叫它。
************************************************************************************************************************
*/

void OS_QClr(OS_Q *p_q)
{
    (void)OS_MsgQFreeAll(&p_q->MsgQ);                           /* 將所有 OS_MSG 返回到空閒列表 */
#if (OS_OBJ_TYPE_REQ > 0u)
    p_q->Type = OS_OBJ_TYPE_NONE;                               /* 將資料結構標記為 NONE */
#endif
#if (OS_CFG_DBG_EN > 0u)
    p_q->NamePtr = (CPU_CHAR *)((void *)"?Q");                  /* 設定名稱指標為 "?Q" 以便除錯 */
#endif
    OS_MsgQInit(&p_q->MsgQ,                                     /* 初始化 OS_MSG 列表 */
                0u);
    OS_PendListInit(&p_q->PendList);                            /* 初始化等待列表 */
}


/*
************************************************************************************************************************
*                                      新增/移除訊息佇列到/從除錯列表
*
* 描述: 這些函式由 uC/OS-III 呼叫,用於將訊息佇列新增到或從訊息佇列除錯列表中移除。
*
* 引數: p_q     是一個指向要新增或移除的訊息佇列的指標
*
* 返回值: 無
*
* 注意: 這些函式是 uC/OS-III 的內部函式,您的應用程式不應呼叫它們。
************************************************************************************************************************
*/

#if (OS_CFG_DBG_EN > 0u)
void  OS_QDbgListAdd (OS_Q  *p_q)
{
    p_q->DbgNamePtr               = (CPU_CHAR *)((void *)" ");
    p_q->DbgPrevPtr               = (OS_Q *)0;
    if (OSQDbgListPtr == (OS_Q *)0) {
        p_q->DbgNextPtr           = (OS_Q *)0;
    } else {
        p_q->DbgNextPtr           =  OSQDbgListPtr;
        OSQDbgListPtr->DbgPrevPtr =  p_q;
    }
    OSQDbgListPtr                 =  p_q;
}


void  OS_QDbgListRemove (OS_Q  *p_q)
{
    OS_Q  *p_q_next;
    OS_Q  *p_q_prev;


    p_q_prev = p_q->DbgPrevPtr;
    p_q_next = p_q->DbgNextPtr;

    if (p_q_prev == (OS_Q *)0) {
        OSQDbgListPtr = p_q_next;
        if (p_q_next != (OS_Q *)0) {
            p_q_next->DbgPrevPtr = (OS_Q *)0;
        }
        p_q->DbgNextPtr = (OS_Q *)0;

    } else if (p_q_next == (OS_Q *)0) {
        p_q_prev->DbgNextPtr = (OS_Q *)0;
        p_q->DbgPrevPtr      = (OS_Q *)0;

    } else {
        p_q_prev->DbgNextPtr =  p_q_next;
        p_q_next->DbgPrevPtr =  p_q_prev;
        p_q->DbgNextPtr      = (OS_Q *)0;
        p_q->DbgPrevPtr      = (OS_Q *)0;
    }
}
#endif
#endif

相關文章