/*****************************************************************************/ /** * @file systembus.c * @author FanHao (hao_fan_bit_ee@163.com) * @brief System bus protocol implementation file. * @version 0.1 * @date 2023-05-06 * * @copyright Copyright(c) 2024 Beijing Institute of Technology.\n * Lab of Communication and Network * ******************************************************************************/ /** * @addtogroup SystemBus * @{ */ /***************************** Include Files *********************************/ #include #include #include #include #include "systembus_uart.h" #include "systembus.h" #include "util.h" #include "common.h" #include "error.h" #include "circ_queue.h" #include "../system_monitor.h" /** * @addtogroup SystemBus_Protocol_Routine * @{ */ /************************** Constant Definitions *****************************/ /** * @name SystemBus_MSG_Constants * @{ */ // Common Field // -- Command word #define WD_CMD 0x00 // -- Production type #define WD_PRD 0x01 #define PRD_DL_K 0xDD #define PRD_DL_C 0xEE #define PRD_ALL 0xFF // Self-Test #define CMD_SELF_TEST 0xE1 // -- Request // -- -- -- // -- Response #define ST_RSP_LEN 23 // -- -- Self-Test Result #define WD_ST_RET 0x02 #define ST_RET_OK 0xAA #define ST_RET_ERROR 0x55 // -- -- Hardware info offset #define SP_INFO_OFFSET 0x03 #define ST_INFO_LEN 20 // Data Echo Test Command #define CMD_ECHO 0xE2 // Configuration #define CMD_CONFIG 0xE3 // -- Request // -- -- Enable control #define WD_EN_CMD 0x02 #define EN_CMD_ENABLE 0xAA #define EN_CMD_SILENT 0x55 // -- -- Node mode control #define WD_ID_CMD 0x03 // -- -- Service Rate control #define WD_RATE_CMD 0x04 #define RATE_CMD_128K 0x22 #define RATE_CMD_512K 0x33 #define RATE_CMD_2M 0x44 // -- -- Routing table select #define WD_RTNE_CMD 0x05 // -- Response // -- -- none response // System info update #define CMD_SYS_INFO 0x33 // -- Request // -- -- 54 byte system info #define SYS_INFO_OFFSET 0x02 // -- Response // -- -- none response // Data Send Command #define CMD_SEND_DATA 0xE4 // -- Request // -- -- destination ID #define SEND_DATA_ID_OFFSET 0x02 #define SEND_DATA_ID_LEN 4 #define WD_DST_ID 0x06 // -- -- data length #define SEND_DATA_LEN_OFFSET 0x07 #define SEND_DATA_LEN_LEN 2 // -- -- <= 1000 byte payload #define SEND_DATA_OFFSET 0x09 // -- Response // -- -- none response // Data Receive Command #define CMD_RECV_DATA 0xE5 // -- Request // -- -- -- // -- Response // -- -- source ID #define RECV_DATA_ID_OFFSET 0x02 #define RECV_DATA_ID_LEN 4 #define WD_SRC_ID 0x06 // -- -- data length #define RECV_DATA_LEN_OFFSET 0x07 #define RECV_DATA_LEN_LEN 2 // -- -- <= 1000 byte payload #define RECV_DATA_OFFSET 0x09 // Remote Test #define CMD_RM_TEST 0xE6 // -- Request // -- -- -- // -- Response #define RM_TEST_RSP_LEN 202 // -- -- 8 byte time stamp #define RT_TIME_STAMP_OFFSET 0x02 #define RT_TIME_STAMP_LEN 8 // -- -- 16 byte system bus log #define RT_SYSBUS_LOG_OFFSET 0x0A #define RT_SYSBUS_LOG_LEN 16 // -- -- 88 byte link layer log #define RT_LINK_LOG_OFFSET 0x1A #define RT_LINK_LOG_LEN 100 // -- -- 56 byte physical layer log #define RT_PHY_LOG_OFFSET 0x7E #define RT_PHY_LOG_LEN 56 // -- -- 20 byte hardware log #define RT_HARDWARE_ST_OFFSET 0xB6 #define RT_HARDWARE_ST_LEN 20 // My Production Type #define MY_PRD_ID PRD_DL_C /** @} SystemBus_MSG_Constants*/ /************************** Function Prototypes ******************************/ static int32_t SystemBus_GenSelfTestRsp(SYSBUS *instPtr); static int32_t SystemBus_SystemConfig(SYSBUS *instPtr, uint8_t *msg); static int32_t SystemBus_Echo(SYSBUS *instPtr, SYSBUS_UART_FRAME *frame); static int32_t SystemBus_GenRemoteTestRsp(SYSBUS *instPtr, uint8_t *msg); /**************************** Type Definitions *******************************/ /************************** Variable Definitions *****************************/ // TODO: better approach? #include "../networking/link_layer/link_layer.h" #include "../networking/phy_layer/phy_layer.h" extern L2_INST *linkInstPtr; extern L2_CONFIG linkConfig; inline bool SystemBus_IsReqPending(SYSBUS *instPtr) { ASSERT(instPtr != NULL); return (!isCircQueueEmpty(instPtr->rxQueue)); } inline bool SystemBus_IsRespPending(SYSBUS *instPtr) { ASSERT(instPtr != NULL); return (!isCircQueueEmpty(instPtr->txQueue)); } inline bool SystemBus_IsTxBusy(SYSBUS *instPtr) { ASSERT(instPtr != NULL); return (!instPtr->isTxDone); } inline bool SystemBus_IsRxBusy(SYSBUS *instPtr) { ASSERT(instPtr != NULL); return SysbusUart_IsRxBusy(instPtr->uartInst); } /** * @brief Generate System Bus SelfTest response. */ static int32_t SystemBus_GenSelfTestRsp(SYSBUS *instPtr) { ASSERT(instPtr != NULL); SYSBUS_UART_FRAME *sysbusRsp = malloc(sizeof(SYSBUS_UART_FRAME)); if (sysbusRsp == NULL) { DBG_ERROR("Failed to allocate memory for SELF TEST Response!\n"); return ENOMEM; } HWSTATE *hwStatsPtr = &(instPtr->hwState); sysbusRsp->len = ST_RSP_LEN; memset(sysbusRsp->data, 0x00, ST_RSP_LEN); sysbusRsp->data[WD_CMD] = CMD_SELF_TEST; sysbusRsp->data[WD_PRD] = MY_PRD_ID; SysMonitor_GetHardwareStatus(hwStatsPtr); if (L2_isStarted(linkInstPtr)) { sysbusRsp->data[WD_ST_RET] = (hwStatsPtr->isSpOk && hwStatsPtr->isAntOk) ? ST_RET_OK : ST_RET_ERROR; } else { sysbusRsp->data[WD_ST_RET] = (hwStatsPtr->isSpOk) ? ST_RET_OK : ST_RET_ERROR; } // apply status and omit 2 bytes of overhead. memcpy(sysbusRsp->data + SP_INFO_OFFSET, &(hwStatsPtr->spFpgaVolt), ST_INFO_LEN); if (CircQueuePush(instPtr->txQueue, (void *)sysbusRsp) == false) { free(sysbusRsp); DBG_ERROR("SystemBus::TX Queue overflowed!\n"); return EQUEUE; } return SUCCESS; } /** * @brief Apply system configuration */ static int32_t SystemBus_SystemConfig(SYSBUS *instPtr, uint8_t *msg) { ASSERT(instPtr != NULL); ASSERT(msg != NULL); PHY_INST *phyRef = linkInstPtr->config.phyInstPtr; TMC_INST *tmcRef = linkInstPtr->config.tmcInstPtr; bool reconfig = false; // Node mode control if (msg[WD_ID_CMD] >= 0 && msg[WD_ID_CMD] < linkConfig.netScale) { reconfig |= (msg[WD_ID_CMD] != linkConfig.myId); linkConfig.myType = (msg[WD_ID_CMD] == 0) ? L2_NODE_HEAD : L2_NODE_MEMBER; linkConfig.myId = msg[WD_ID_CMD]; } else { // Ignore current command DBG_ERROR("Unexpected config command 0x%02X:0x%02X", WD_ID_CMD, msg[WD_ID_CMD]); return EPARAM; } // Service Rate control switch (msg[WD_RATE_CMD]) { case RATE_CMD_128K: { reconfig |= (linkConfig.serviceRate != L2_SVC_128K); linkConfig.serviceRate = L2_SVC_128K; break; } case RATE_CMD_512K: { reconfig |= (linkConfig.serviceRate != L2_SVC_512K); linkConfig.serviceRate = L2_SVC_512K; break; } case RATE_CMD_2M: { reconfig |= (linkConfig.serviceRate != L2_SVC_2M); linkConfig.serviceRate = L2_SVC_2M; break; } default: { // Ignore current command DBG_ERROR("Unexpected config command 0x%02X:0x%02X", WD_RATE_CMD, msg[WD_RATE_CMD]); return EPARAM; } } // Enable control if (msg[WD_EN_CMD] == EN_CMD_ENABLE) { if (reconfig || !L2_isStarted(linkInstPtr)) { Linklayer_Reconfig(linkInstPtr, &linkConfig); } Linklayer_Start(linkInstPtr); } else if (msg[WD_EN_CMD] == EN_CMD_SILENT) { Linklayer_Halt(linkInstPtr); // configure phy to generate remote test TIME_WORD pullTime = {0, 0}; pullTime.int_part = TimeCtrl_GetIntCnt(tmcRef) + 1; if (Phy_SetTimeWord(phyRef, &pullTime, PHY_TIME_WORD_POLL) != SUCCESS) { DBG_ERROR("Physical set pull time failed.\n"); } Phy_SetupNextSlot(phyRef, PHY_TOR_RX, L1_FRAME_ADV_24MS, NULL); } else { // Ignore current command DBG_ERROR("Unexpected config command 0x%02X:0x%02X", WD_EN_CMD, msg[WD_EN_CMD]); return EIGNORE; } // Routing table selection if (msg[WD_RTNE_CMD] != instPtr->sysbusLog.lastConfig[3]) { L2_RTNE_CONFIG newRtneConfig; switch (msg[WD_RTNE_CMD]) { case 0: { newRtneConfig.rtneTable = normalTable0; newRtneConfig.rtnePrd = ARRAY_SIZE(normalTable0); L2_EVENT l2Event = { .event = L2_ROUTINE_UPDATE, .msg = &newRtneConfig }; AssertL2Event(linkInstPtr, &l2Event); break; } case 1: { newRtneConfig.rtneTable = normalTable1; newRtneConfig.rtnePrd = ARRAY_SIZE(normalTable1); L2_EVENT l2Event = { .event = L2_ROUTINE_UPDATE, .msg = &newRtneConfig }; AssertL2Event(linkInstPtr, &l2Event); break; } case 2: { newRtneConfig.rtneTable = normalTable2; newRtneConfig.rtnePrd = ARRAY_SIZE(normalTable2); L2_EVENT l2Event = { .event = L2_ROUTINE_UPDATE, .msg = &newRtneConfig }; AssertL2Event(linkInstPtr, &l2Event); break; } default: { // Ignore current command DBG_ERROR("Unexpected config command 0x%02X:0x%02X", WD_RTNE_CMD, msg[WD_RTNE_CMD]); return EIGNORE; } } } instPtr->sysbusLog.lastConfig[0] = msg[WD_EN_CMD]; instPtr->sysbusLog.lastConfig[1] = msg[WD_ID_CMD]; instPtr->sysbusLog.lastConfig[2] = msg[WD_RATE_CMD]; instPtr->sysbusLog.lastConfig[3] = msg[WD_RTNE_CMD]; return SUCCESS; } /** * @brief Echo System Bus frame */ static int32_t SystemBus_Echo(SYSBUS *instPtr, SYSBUS_UART_FRAME *frame) { ASSERT(instPtr != NULL); ASSERT(frame != NULL); SYSBUS_UART_FRAME *sysbusRsp = malloc(sizeof(SYSBUS_UART_FRAME)); if (sysbusRsp == NULL) { DBG_ERROR("Failed to allocate memory for ECHO TEST Response!\n"); return ENOMEM; } sysbusRsp->len = frame->len; memcpy(sysbusRsp->data, frame->data, sysbusRsp->len); if (CircQueuePush(instPtr->txQueue, (void *)sysbusRsp) == false) { free(sysbusRsp); DBG_ERROR("SystemBus::TX Queue overflowed!\n"); return EQUEUE; } return SUCCESS; } /** * @brief Generate System Bus receive call command response */ int32_t SystemBus_GenRecvRsp(SYSBUS *instPtr, SYSMON_SVC_PAYLOAD *payload) { ASSERT(instPtr != NULL); ASSERT(payload != NULL); SYSBUS_UART_FRAME *sysbusRsp = malloc(sizeof(SYSBUS_UART_FRAME)); if (sysbusRsp == NULL) { DBG_ERROR("Failed to allocate memory for Receive Response!\n"); return ENOMEM; } sysbusRsp->len = payload->dataLen + RECV_DATA_LEN_OFFSET; sysbusRsp->data[WD_CMD] = CMD_RECV_DATA; sysbusRsp->data[WD_PRD] = MY_PRD_ID; memcpy(sysbusRsp->data + RECV_DATA_ID_OFFSET, payload, sizeof(SYSMON_SVC_PAYLOAD)); if (CircQueuePush(instPtr->txQueue, (void *)sysbusRsp) == false) { free(sysbusRsp); DBG_WARNING("SystemBus::TX Queue overflowed!\n"); return EQUEUE; } instPtr->sysbusLog.recvCnt++; return SUCCESS; } /** * @brief Generate System Bus remote test command response */ static int32_t SystemBus_GenRemoteTestRsp(SYSBUS *instPtr, uint8_t *msg) { ASSERT(instPtr != NULL); ASSERT(msg != NULL); SYSBUS_UART_FRAME *sysbusRsp = malloc(sizeof(SYSBUS_UART_FRAME)); if (sysbusRsp == NULL) { DBG_ERROR("Failed to allocate memory for LJ REMOTE TEST Response!\n"); return ENOMEM; } TIME_WORD tmWord; uint64_t time; TimeCtrl_GetCurrentTime(linkInstPtr->config.tmcInstPtr, &tmWord); time = (uint64_t)tmWord.int_part * TIME_FRAC_MAX + tmWord.frac_part; if (msg[WD_CMD] == CMD_RM_TEST) { sysbusRsp->len = RM_TEST_RSP_LEN; memset(sysbusRsp->data, 0x00, RM_TEST_RSP_LEN); sysbusRsp->data[WD_CMD] = CMD_RM_TEST; sysbusRsp->data[WD_PRD] = MY_PRD_ID; memcpy(sysbusRsp->data + RT_TIME_STAMP_OFFSET, &time, RT_TIME_STAMP_LEN); SysMonitor_GetLinklayerLog(&(instPtr->linkLog)); SysMonitor_GetPhysicalLayerLog(&(instPtr->phyLog)); SysMonitor_GetHardwareStatus(&(instPtr->hwState)); memcpy(sysbusRsp->data + RT_SYSBUS_LOG_OFFSET, &(instPtr->sysbusLog), RT_SYSBUS_LOG_LEN); memcpy(sysbusRsp->data + RT_LINK_LOG_OFFSET, &(instPtr->linkLog), RT_LINK_LOG_LEN); memcpy(sysbusRsp->data + RT_PHY_LOG_OFFSET, &(instPtr->phyLog), RT_PHY_LOG_LEN); memcpy(sysbusRsp->data + RT_HARDWARE_ST_OFFSET, &(instPtr->hwState.spFpgaVolt), RT_HARDWARE_ST_LEN); } else { DBG_ERROR("Unexpected command 0x%02X.\n", msg[WD_CMD]); free(sysbusRsp); return EUNEXP; } if (CircQueuePush(instPtr->txQueue, (void *)sysbusRsp) == false) { free(sysbusRsp); DBG_WARNING("SystemBus::TX Queue overflowed!\n"); return EQUEUE; } instPtr->sysbusLog.remoteCnt++; return SUCCESS; } /*****************************************************************************/ /** * @brief A handler function. This handler evaluate the message from system * bus receive queue, and make appropriate response. * * @param instPtr A pointer to System Bus instance. * * @return None * ******************************************************************************/ int32_t SystemBus_HandleCmd(SYSBUS *instPtr) { ASSERT(instPtr != NULL); int32_t Status; SYSBUS_UART_FRAME *sysbusReq; uint8_t *msg; if (CircQueuePop(instPtr->rxQueue, (void **)(&sysbusReq)) == false) { DBG_WARNING("SystemBus::Nothing to be handled.\n"); return EIGNORE; } msg = sysbusReq->data; if (msg[WD_PRD] != MY_PRD_ID && msg[WD_PRD] != PRD_ALL) { DBG_WARNING("SystemBus::Production code mismatch!\n"); return EIGNORE; } switch (msg[WD_CMD]) { case CMD_SELF_TEST: { Status = SystemBus_GenSelfTestRsp(instPtr); break; } case CMD_ECHO: { Status = SystemBus_Echo(instPtr, sysbusReq); break; } case CMD_CONFIG: { Status = SystemBus_SystemConfig(instPtr, msg); break; } case CMD_SYS_INFO: { SysMonitor_UpdateSysinfo((SYSINFO *)(msg + SYS_INFO_OFFSET)); Status = SUCCESS; break; } case CMD_SEND_DATA: { Status = SysMonitor_UpdateSendPayload((SYSMON_SVC_PAYLOAD *)(msg + SEND_DATA_ID_OFFSET)); if (Status == SUCCESS) { instPtr->sysbusLog.sendCnt++; } break; } case CMD_RM_TEST: { Status = SystemBus_GenRemoteTestRsp(instPtr, msg); break; } default: { DBG_ERROR("SystemBus::Unknown command code!\n"); Status = FAILURE; break; } } free(sysbusReq); return Status; } /*****************************************************************************/ /** * @brief This function send a repose frame from system bus tx queue. * * @param instPtr A pointer to System Bus instance. * * @return None * ******************************************************************************/ int32_t SystemBus_MakeResp(SYSBUS *instPtr) { ASSERT(instPtr != NULL); int32_t Status; SYSBUS_UART_FRAME *sysbusRsp; if (CircQueuePop(instPtr->txQueue, (void **)(&sysbusRsp)) == false) { DBG_WARNING("SystemBus::Nothing to be sent.\n"); return EIGNORE; } instPtr->isTxDone = false; // A transmission procedure is started. Status = SysbusUart_Send(instPtr->uartInst, sysbusRsp); free(sysbusRsp); return Status; } /** @} */ /*****************************************************************************/ /** * @brief Initialize System Bus instance. * * @param InstPtr A double pointer to System Bus instance. * @param config A pointer to System Bus configurations. * * @return Error code. * ******************************************************************************/ int32_t SystemBus_Init(SYSBUS **instPtr, SYSBUS_CONFIG *config) { ASSERT(instPtr != NULL); ASSERT(config != NULL); SYSBUS_UART_CONFIG uartConfig; int32_t Status; (*instPtr) = malloc(sizeof(SYSBUS)); if ((*instPtr) == NULL) { return ENOMEM; } (*instPtr)->rxQueue = NULL; (*instPtr)->txQueue = NULL; (*instPtr)->uartInst = NULL; memcpy(&((*instPtr)->config), config, sizeof(SYSBUS_CONFIG)); memset(&((*instPtr)->sysbusLog), 0x00, sizeof(SYSBUSLOG)); memset(&((*instPtr)->linkLog), 0x00, sizeof(LINKLOG)); memset(&((*instPtr)->phyLog), 0x00, sizeof(PHYLOG)); memset(&((*instPtr)->hwState), 0x00, sizeof(HWSTATE)); uartConfig.baseAddr = config->baseAddr; Status = SysbusUart_Init(&((*instPtr)->uartInst), &uartConfig); if (Status != SUCCESS) { SystemBus_Destroy(*instPtr); return ENOMEM; } if (CircQueueCreate(&((*instPtr)->txQueue), (*instPtr)->config.txQueueMax) == false) { SystemBus_Destroy(*instPtr); return ENOMEM; } if (CircQueueCreate(&((*instPtr)->rxQueue), (*instPtr)->config.rxQueueMax) == false) { SystemBus_Destroy(*instPtr); return ENOMEM; } (*instPtr)->isRxDone = false; // nothing received. (*instPtr)->isTxDone = true; // nothing to be sent. return SUCCESS; } /*****************************************************************************/ /** * @brief Destroy System Bus instance. * * @param InstPtr A pointer to System Bus instance. * * @return None. * ******************************************************************************/ void SystemBus_Destroy(SYSBUS *instPtr) { if (instPtr == NULL) { return; } free(instPtr->uartInst); free(instPtr->txQueue); free(instPtr->rxQueue); free(instPtr); } /*****************************************************************************/ /** * @brief This function handles the systembus tx irq events. * * @param instPtr A pointer to System Bus instance. * * @return None * ******************************************************************************/ int32_t SystemBus_TxIntrHandler(SYSBUS *instPtr) { ASSERT(instPtr != NULL); // nothing special... instPtr->isTxDone = true; // A transmission procedure finished. return SUCCESS; } /*****************************************************************************/ /** * @brief This function handles the systembus rx irq events. * * @param instPtr A pointer to System Bus instance. * * @return None * ******************************************************************************/ int32_t SystemBus_RxIntrHandler(SYSBUS *instPtr) { ASSERT(instPtr != NULL); instPtr->isRxDone = true; // whether reception successes or not, receiving procedure ended. int32_t Status; SYSBUS_UART_FRAME *sysbusReq = malloc(sizeof(SYSBUS_UART_FRAME)); if (sysbusReq == NULL) { return ENOMEM; } Status = SysbusUart_Recv(instPtr->uartInst, sysbusReq); if (Status != SUCCESS) { free(sysbusReq); return Status; } if (CircQueuePush(instPtr->rxQueue, (void *)sysbusReq) == false) { DBG_ERROR("SystemBus::RX Queue overflowed!\n"); free(sysbusReq); return EQUEUE; } instPtr->isRxDone = false; // another reception is undergoing. return SUCCESS; } /** @} SystemBus*/