/* Copyright 2025 muzkr https://github.com/muzkr * Copyright 2023 Dual Tachyon * https://github.com/DualTachyon * * 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 #if !defined(ENABLE_OVERLAY) #include "py32f0xx.h" #endif #ifdef ENABLE_FMRADIO #include "app/fm.h" #endif #include "app/uart.h" #include "board.h" #include "py32f071_ll_dma.h" #include "driver/backlight.h" #include "driver/bk4819.h" #include "driver/crc.h" #include "driver/eeprom.h" #include "driver/gpio.h" #if defined(ENABLE_UART) #include "driver/uart.h" #endif #if defined(ENABLE_USB) #include "driver/vcp.h" #endif #include "functions.h" #include "misc.h" #include "settings.h" #include "version.h" #ifdef ENABLE_FEAT_F4HWN_MULTIBOOT #include "driver/mb_flash.h" #endif #if defined(ENABLE_OVERLAY) #include "sram-overlay.h" #endif #define UNUSED(x) (void)(x) #define DMA_INDEX(x, y, z) (((x) + (y)) % (z)) #if defined(ENABLE_UART) #define DMA_CHANNEL LL_DMA_CHANNEL_2 #endif // !! Make sure this is correct! #define MAX_REPLY_SIZE 144 typedef struct { uint16_t ID; uint16_t Size; } Header_t; typedef struct { uint8_t Padding[2]; uint16_t ID; } Footer_t; typedef struct { Header_t Header; uint32_t Timestamp; } CMD_0514_t; typedef struct { Header_t Header; struct { char Version[16]; bool bHasCustomAesKey; bool bIsInLockScreen; uint8_t Padding[2]; uint32_t Challenge[4]; } Data; } REPLY_0514_t; typedef struct { Header_t Header; uint16_t Offset; uint8_t Size; uint8_t Padding; uint32_t Timestamp; } CMD_051B_t; typedef struct { Header_t Header; struct { uint16_t Offset; uint8_t Size; uint8_t Padding; uint8_t Data[128]; } Data; } REPLY_051B_t; typedef struct { Header_t Header; uint16_t Offset; uint8_t Size; bool bAllowPassword; uint32_t Timestamp; uint8_t Data[0]; } CMD_051D_t; typedef struct { Header_t Header; struct { uint16_t Offset; } Data; } REPLY_051D_t; #ifdef ENABLE_EXTRA_UART_CMD typedef struct { Header_t Header; struct { uint16_t RSSI; uint8_t ExNoiseIndicator; uint8_t GlitchIndicator; } Data; } REPLY_0527_t; typedef struct { Header_t Header; struct { uint16_t Voltage; uint16_t Current; } Data; } REPLY_0529_t; typedef struct { Header_t Header; uint32_t Response[4]; } CMD_052D_t; #endif typedef struct { Header_t Header; struct { bool bIsLocked; uint8_t Padding[3]; } Data; } REPLY_052D_t; #ifdef ENABLE_EXTRA_UART_CMD typedef struct { Header_t Header; uint32_t Timestamp; } CMD_052F_t; #endif static const uint8_t Obfuscation[16] = { 0x16, 0x6C, 0x14, 0xE6, 0x2E, 0x91, 0x0D, 0x40, 0x21, 0x35, 0xD5, 0x40, 0x13, 0x03, 0xE9, 0x80 }; typedef union { uint8_t Buffer[256]; struct { Header_t Header; uint8_t Data[252]; }; } UART_Command_t __attribute__ ((aligned (4))); #if defined(ENABLE_UART) static uint32_t UART_Timestamp; static UART_Command_t UART_Command; static uint16_t gUART_WriteIndex; #endif #if defined(ENABLE_USB) static uint32_t VCP_Timestamp; static UART_Command_t VCP_Command; static uint16_t VCP_ReadIndex; #endif // static bool bIsEncrypted = true; #define bIsEncrypted true #ifdef ENABLE_USB static void SendReply_VCP(void *pReply, uint16_t Size) { static uint8_t VCP_ReplyBuf[MAX_REPLY_SIZE + sizeof(Header_t) + sizeof(Footer_t)] __attribute__((aligned(4))); // !! if (Size > MAX_REPLY_SIZE) { return; } uint8_t *pBody = VCP_ReplyBuf + sizeof(Header_t); uint8_t *pFooter = pBody + Size; memcpy(pBody, pReply, Size); pReply = pBody; if (bIsEncrypted) { uint8_t *pBytes = (uint8_t *)pReply; unsigned int i; for (i = 0; i < Size; i++) pBytes[i] ^= Obfuscation[i % 16]; } /* Build the transport header/footer byte by byte. The reply body may have * an odd size, so pFooter is not necessarily half-word aligned; casting it * to Footer_t and storing ID as uint16_t can HardFault on Cortex-M0+. */ VCP_ReplyBuf[0] = 0xAB; VCP_ReplyBuf[1] = 0xCD; VCP_ReplyBuf[2] = (uint8_t)(Size & 0xFFu); VCP_ReplyBuf[3] = (uint8_t)(Size >> 8); // VCP_Send((uint8_t *)&Header, sizeof(Header)); // VCP_Send(pReply, Size); if (bIsEncrypted) { pFooter[0] = Obfuscation[(Size + 0) % 16] ^ 0xFF; pFooter[1] = Obfuscation[(Size + 1) % 16] ^ 0xFF; } else { pFooter[0] = 0xFF; pFooter[1] = 0xFF; } pFooter[2] = 0xDC; pFooter[3] = 0xBA; // VCP_Send((uint8_t *)&Footer, sizeof(Footer)); VCP_SendAsync(VCP_ReplyBuf, sizeof(Header_t) + Size + sizeof(Footer_t)); } #endif // ENABLE_USB static void SendReply(uint32_t Port, void *pReply, uint16_t Size) { #if defined(ENABLE_USB) if (Port == UART_PORT_VCP) { SendReply_VCP(pReply, Size); return; } #endif Header_t Header; Footer_t Footer; if (bIsEncrypted) { uint8_t *pBytes = (uint8_t *)pReply; unsigned int i; for (i = 0; i < Size; i++) pBytes[i] ^= Obfuscation[i % 16]; } Header.ID = 0xCDAB; Header.Size = Size; UART_Send(&Header, sizeof(Header)); UART_Send(pReply, Size); if (bIsEncrypted) { Footer.Padding[0] = Obfuscation[(Size + 0) % 16] ^ 0xFF; Footer.Padding[1] = Obfuscation[(Size + 1) % 16] ^ 0xFF; } else { Footer.Padding[0] = 0xFF; Footer.Padding[1] = 0xFF; } Footer.ID = 0xBADC; UART_Send(&Footer, sizeof(Footer)); } static void SendVersion(uint32_t Port) { REPLY_0514_t Reply; Reply.Header.ID = 0x0515; Reply.Header.Size = sizeof(Reply.Data); strcpy(Reply.Data.Version, Version); Reply.Data.bHasCustomAesKey = bHasCustomAesKey; Reply.Data.bIsInLockScreen = bIsInLockScreen; Reply.Data.Challenge[0] = gChallenge[0]; Reply.Data.Challenge[1] = gChallenge[1]; Reply.Data.Challenge[2] = gChallenge[2]; Reply.Data.Challenge[3] = gChallenge[3]; SendReply(Port, &Reply, sizeof(Reply)); } #ifndef ENABLE_FEAT_F4HWN static bool IsBadChallenge(const uint32_t *pKey, const uint32_t *pIn, const uint32_t *pResponse) { // PY32 has no AES hardware /* unsigned int i; uint32_t IV[4]; IV[0] = 0; IV[1] = 0; IV[2] = 0; IV[3] = 0; AES_Encrypt(pKey, IV, pIn, IV, true); for (i = 0; i < 4; i++) if (IV[i] != pResponse[i]) return true; */ return false; } #endif // session init, sends back version info and state // timestamp is a session id really static void CMD_0514(uint32_t Port, const uint8_t *pBuffer) { const CMD_0514_t *pCmd = (const CMD_0514_t *)pBuffer; if(0) {} #if defined(ENABLE_UART) else if (Port == UART_PORT_UART) { UART_Timestamp = pCmd->Timestamp; } #endif #if defined(ENABLE_USB) else if (Port == UART_PORT_VCP) { VCP_Timestamp = pCmd->Timestamp; } #endif #ifdef ENABLE_FMRADIO gFmRadioCountdown_500ms = fm_radio_countdown_500ms; #endif gSerialConfigCountDown_500ms = 12; // 6 sec if (gEeprom.BACKLIGHT_TIME < 61) // backlight is set to be always on BACKLIGHT_TurnOff(); // turn the LCD backlight off SendVersion(Port); } // read eeprom static void CMD_051B(uint32_t Port, const uint8_t *pBuffer) { const CMD_051B_t *pCmd = (const CMD_051B_t *)pBuffer; REPLY_051B_t Reply; bool bLocked = false; uint32_t Timestamp = 0; if(0) {} #if defined(ENABLE_UART) else if (Port == UART_PORT_UART) { Timestamp = UART_Timestamp; } #endif #if defined(ENABLE_USB) else if (Port == UART_PORT_VCP) { Timestamp = VCP_Timestamp; } #endif else { return; } if (pCmd->Timestamp != Timestamp) return; gSerialConfigCountDown_500ms = 12; // 6 sec #ifdef ENABLE_FMRADIO gFmRadioCountdown_500ms = fm_radio_countdown_500ms; #endif memset(&Reply, 0, sizeof(Reply)); Reply.Header.ID = 0x051C; Reply.Header.Size = pCmd->Size + 4; Reply.Data.Offset = pCmd->Offset; Reply.Data.Size = pCmd->Size; if (bHasCustomAesKey) bLocked = gIsLocked; if (!bLocked) { EEPROM_ReadBuffer(pCmd->Offset, Reply.Data.Data, pCmd->Size); } SendReply(Port, &Reply, pCmd->Size + 8); } // write eeprom static void CMD_051D(uint32_t Port, const uint8_t *pBuffer) { const CMD_051D_t *pCmd = (const CMD_051D_t *)pBuffer; REPLY_051D_t Reply; bool bReloadEeprom; bool bIsLocked; uint32_t Timestamp = 0; if(0) {} #if defined(ENABLE_UART) else if (Port == UART_PORT_UART) { Timestamp = UART_Timestamp; } #endif #if defined(ENABLE_USB) else if (Port == UART_PORT_VCP) { Timestamp = VCP_Timestamp; } #endif else { return; } if (pCmd->Timestamp != Timestamp) return; gSerialConfigCountDown_500ms = 12; // 6 sec bReloadEeprom = false; #ifdef ENABLE_FMRADIO gFmRadioCountdown_500ms = fm_radio_countdown_500ms; #endif Reply.Header.ID = 0x051E; Reply.Header.Size = sizeof(Reply.Data); Reply.Data.Offset = pCmd->Offset; bIsLocked = bHasCustomAesKey ? gIsLocked : false; if (!bIsLocked) { unsigned int i; for (i = 0; i < (pCmd->Size / 8); i++) { const uint16_t Offset = pCmd->Offset + (i * 8U); if (Offset >= 0x0F30 && Offset < 0x0F40) if (!gIsLocked) bReloadEeprom = true; if ((Offset < 0x0E98 || Offset >= 0x0EA0) || !bIsInLockScreen || pCmd->bAllowPassword) { EEPROM_WriteBuffer(Offset, &pCmd->Data[i * 8U]); } } if (bReloadEeprom) SETTINGS_InitEEPROM(); } SendReply(Port, &Reply, sizeof(Reply)); } #ifdef ENABLE_EXTRA_UART_CMD // read RSSI static void CMD_0527(uint32_t Port) { REPLY_0527_t Reply; Reply.Header.ID = 0x0528; Reply.Header.Size = sizeof(Reply.Data); Reply.Data.RSSI = BK4819_ReadRegister(BK4819_REG_67) & 0x01FF; Reply.Data.ExNoiseIndicator = BK4819_ReadRegister(BK4819_REG_65) & 0x007F; Reply.Data.GlitchIndicator = BK4819_ReadRegister(BK4819_REG_63); SendReply(Port, &Reply, sizeof(Reply)); } // read ADC static void CMD_0529(uint32_t Port) { REPLY_0529_t Reply; Reply.Header.ID = 0x52A; Reply.Header.Size = sizeof(Reply.Data); // Original doesn't actually send current! BOARD_ADC_GetBatteryInfo(&Reply.Data.Voltage, &Reply.Data.Current); SendReply(Port, &Reply, sizeof(Reply)); } #ifndef ENABLE_FEAT_F4HWN static void CMD_052D(uint32_t Port, const uint8_t *pBuffer) { const CMD_052D_t *pCmd = (const CMD_052D_t *)pBuffer; REPLY_052D_t Reply; bool bIsLocked; #ifdef ENABLE_FMRADIO gFmRadioCountdown_500ms = fm_radio_countdown_500ms; #endif Reply.Header.ID = 0x052E; Reply.Header.Size = sizeof(Reply.Data); bIsLocked = bHasCustomAesKey; if (!bIsLocked) bIsLocked = IsBadChallenge(gCustomAesKey, gChallenge, pCmd->Response); if (!bIsLocked) { bIsLocked = IsBadChallenge(gDefaultAesKey, gChallenge, pCmd->Response); if (bIsLocked) gTryCount++; } if (gTryCount < 3) { if (!bIsLocked) gTryCount = 0; } else { gTryCount = 3; bIsLocked = true; } gIsLocked = bIsLocked; Reply.Data.bIsLocked = bIsLocked; SendReply(Port, &Reply, sizeof(Reply)); } #endif // session init, sends back version info and state // timestamp is a session id really // this command also disables dual watch, crossband, // DTMF side tones, freq reverse, PTT ID, DTMF decoding, frequency offset // exits power save, sets main VFO to upper, static void CMD_052F(uint32_t Port, const uint8_t *pBuffer) { const CMD_052F_t *pCmd = (const CMD_052F_t *)pBuffer; gEeprom.DUAL_WATCH = DUAL_WATCH_OFF; gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF; gEeprom.RX_VFO = 0; gEeprom.DTMF_SIDE_TONE = false; gEeprom.VfoInfo[0].FrequencyReverse = false; gEeprom.VfoInfo[0].pRX = &gEeprom.VfoInfo[0].freq_config_RX; gEeprom.VfoInfo[0].pTX = &gEeprom.VfoInfo[0].freq_config_TX; gEeprom.VfoInfo[0].TX_OFFSET_FREQUENCY_DIRECTION = TX_OFFSET_FREQUENCY_DIRECTION_OFF; gEeprom.VfoInfo[0].DTMF_PTT_ID_TX_MODE = PTT_ID_OFF; #ifdef ENABLE_DTMF_CALLING gEeprom.VfoInfo[0].DTMF_DECODING_ENABLE = false; #endif #ifdef ENABLE_NOAA gIsNoaaMode = false; #endif if (gCurrentFunction == FUNCTION_POWER_SAVE) FUNCTION_Select(FUNCTION_FOREGROUND); gSerialConfigCountDown_500ms = 12; // 6 sec if(0) {} #if defined(ENABLE_UART) else if (Port == UART_PORT_UART) { UART_Timestamp = pCmd->Timestamp; } #endif #if defined(ENABLE_USB) else if (Port == UART_PORT_VCP) { VCP_Timestamp = pCmd->Timestamp; } #endif if (gEeprom.BACKLIGHT_TIME < 61) // backlight is set to be always on BACKLIGHT_TurnOff(); // turn the LCD backlight off SendVersion(Port); } #endif #ifdef ENABLE_UART_RW_BK_REGS static void CMD_0601_ReadBK4819Reg(uint32_t Port, const uint8_t *pBuffer) { typedef struct __attribute__((__packed__)) { Header_t header; uint8_t reg; } CMD_0601_t; CMD_0601_t *cmd = (CMD_0601_t*) pBuffer; struct __attribute__((__packed__)) { Header_t header; struct __attribute__((__packed__)) { uint8_t reg; uint16_t value; } data; } reply; reply.header.ID = 0x0601; reply.header.Size = sizeof(reply.data); reply.data.reg = cmd->reg; reply.data.value = BK4819_ReadRegister(cmd->reg); SendReply(Port, &reply, sizeof(reply)); } static void CMD_0602_WriteBK4819Reg(const uint8_t *pBuffer) { typedef struct __attribute__((__packed__)) { Header_t header; uint8_t reg; uint16_t value; } CMD_0602_t; CMD_0602_t *cmd = (CMD_0602_t*) pBuffer; BK4819_WriteRegister(cmd->reg, cmd->value); } #endif bool UART_IsCommandAvailable(uint32_t Port) { uint16_t Index; uint16_t TailIndex; uint16_t Size; uint16_t Crc; uint16_t CommandLength; uint16_t DmaLength; uint8_t *ReadBuf; uint16_t ReadBufSize; uint16_t *pReadPointer; UART_Command_t *pUART_Command; if(0){} #if defined(ENABLE_UART) else if (Port == UART_PORT_UART) { DmaLength = sizeof(UART_DMA_Buffer) - LL_DMA_GetDataLength(DMA1, DMA_CHANNEL); ReadBuf = UART_DMA_Buffer; ReadBufSize = sizeof(UART_DMA_Buffer); pReadPointer = &gUART_WriteIndex; pUART_Command = &UART_Command; } #endif #if defined(ENABLE_USB) else if (Port == UART_PORT_VCP) { DmaLength = VCP_RxBufPointer; ReadBuf = VCP_RxBuf; ReadBufSize = sizeof(VCP_RxBuf); pReadPointer = &VCP_ReadIndex; pUART_Command = &VCP_Command; } #endif else { return false; } // Limit iterations to prevent long loops when buffer is full of non-command data uint16_t maxIterations = ReadBufSize + 1; while (maxIterations--) { if ((*pReadPointer) == DmaLength) return false; // Find 0xAB with iteration limit uint16_t searchLimit = ReadBufSize; while ((*pReadPointer) != DmaLength && ReadBuf[*pReadPointer] != 0xABU && searchLimit--) *pReadPointer = DMA_INDEX((*pReadPointer), 1, ReadBufSize); if (searchLimit == 0) { // Too many bytes without finding 0xAB - sync to current position and exit *pReadPointer = DmaLength; return false; } if ((*pReadPointer) == DmaLength) return false; if ((*pReadPointer) < DmaLength) CommandLength = DmaLength - (*pReadPointer); else CommandLength = (DmaLength + ReadBufSize) - (*pReadPointer); if (CommandLength < 8) return 0; if (ReadBuf[DMA_INDEX(*pReadPointer, 1, ReadBufSize)] == 0xCD) break; *pReadPointer = DMA_INDEX(*pReadPointer, 1, ReadBufSize); } if (maxIterations == 0) { // Safety: too many outer loop iterations *pReadPointer = DmaLength; return false; } Index = DMA_INDEX(*pReadPointer, 2, ReadBufSize); Size = (ReadBuf[DMA_INDEX(Index, 1, ReadBufSize)] << 8) | ReadBuf[Index]; if ((Size + 8u) > ReadBufSize) { *pReadPointer = DmaLength; return false; } if (CommandLength < (Size + 8)) return false; Index = DMA_INDEX(Index, 2, ReadBufSize); TailIndex = DMA_INDEX(Index, Size + 2, ReadBufSize); if (ReadBuf[TailIndex] != 0xDC || ReadBuf[DMA_INDEX(TailIndex, 1, ReadBufSize)] != 0xBA) { *pReadPointer = DmaLength; return false; } if (TailIndex < Index) { const uint16_t ChunkSize = ReadBufSize - Index; memcpy(pUART_Command->Buffer, ReadBuf + Index, ChunkSize); memcpy(pUART_Command->Buffer + ChunkSize, ReadBuf, TailIndex); } else memcpy(pUART_Command->Buffer, ReadBuf + Index, TailIndex - Index); TailIndex = DMA_INDEX(TailIndex, 2, ReadBufSize); if (TailIndex < (*pReadPointer)) { memset(ReadBuf + (*pReadPointer), 0, ReadBufSize - (*pReadPointer)); memset(ReadBuf, 0, TailIndex); } else memset(ReadBuf + (*pReadPointer), 0, TailIndex - (*pReadPointer)); *pReadPointer = TailIndex; /* -- if (pUART_Command->Header.ID == 0x0514) bIsEncrypted = false; if (pUART_Command->Header.ID == 0x6902) bIsEncrypted = true; -- */ if (bIsEncrypted) { unsigned int i; for (i = 0; i < (Size + 2u); i++) pUART_Command->Buffer[i] ^= Obfuscation[i % 16]; } Crc = pUART_Command->Buffer[Size] | (pUART_Command->Buffer[Size + 1] << 8); return CRC_Calculate(pUART_Command->Buffer, Size) == Crc; } #ifdef ENABLE_FEAT_F4HWN_MULTIBOOT /* Timestamp latched by the device-info handshake (0x0514) for this port. Slot * writes/erases require it to match, like the EEPROM write command (CMD_051D). */ static uint32_t mb_port_timestamp(uint32_t Port) { #if defined(ENABLE_UART) if (Port == UART_PORT_UART) return UART_Timestamp; #endif #if defined(ENABLE_USB) if (Port == UART_PORT_VCP) return VCP_Timestamp; #endif (void)Port; return 0; } #endif void UART_HandleCommand(uint32_t Port) { UART_Command_t *pUART_Command; if (0) {} #if defined(ENABLE_UART) else if (Port == UART_PORT_UART) { pUART_Command = &UART_Command; } #endif #if defined(ENABLE_USB) else if (Port == UART_PORT_VCP) { pUART_Command = &VCP_Command; } #endif else { return; } switch (pUART_Command->Header.ID) { case 0x0514: CMD_0514(Port, pUART_Command->Buffer); break; case 0x051B: CMD_051B(Port, pUART_Command->Buffer); break; case 0x051D: CMD_051D(Port, pUART_Command->Buffer); break; case 0x051F: // Not implementing non-authentic command break; case 0x0521: // Not implementing non-authentic command break; #ifdef ENABLE_EXTRA_UART_CMD case 0x0527: CMD_0527(Port); break; case 0x0529: CMD_0529(Port); break; #ifndef ENABLE_FEAT_F4HWN case 0x052D: CMD_052D(Port, pUART_Command->Buffer); break; #endif case 0x052F: CMD_052F(Port, pUART_Command->Buffer); break; #endif case 0x05DD: // reset #if defined(ENABLE_OVERLAY) overlay_FLASH_RebootToBootloader(); #else NVIC_SystemReset(); #endif break; #ifdef ENABLE_FEAT_F4HWN_MULTIBOOT // ---- M4 slot management ("Firmware Slots") ------------------------ case 0x0720: // slot info: read the 64-byte header only (fast, no CRC) { gSerialConfigCountDown_500ms = 12; // keep serial mode alive (6 s) uint8_t slot = pUART_Command->Data[0]; mb_slot_header_t hdr; memset(&hdr, 0, sizeof(hdr)); uint8_t status = MB_SlotInfo(slot, &hdr); struct __attribute__((packed)) { Header_t Header; uint8_t Slot; uint8_t Status; uint8_t Hdr[sizeof(mb_slot_header_t)]; } Reply; Reply.Header.ID = 0x0721; Reply.Header.Size = 2 + sizeof(mb_slot_header_t); Reply.Slot = slot; Reply.Status = status; memcpy(Reply.Hdr, &hdr, sizeof(hdr)); SendReply(Port, &Reply, sizeof(Reply)); break; } case 0x0722: // slot erase: wipe the whole 128 KiB slot region { gSerialConfigCountDown_500ms = 12; // keep serial mode alive (6 s) uint8_t slot = pUART_Command->Data[0]; uint32_t ts = (uint32_t)pUART_Command->Data[2] | ((uint32_t)pUART_Command->Data[3] << 8) | ((uint32_t)pUART_Command->Data[4] << 16) | ((uint32_t)pUART_Command->Data[5] << 24); uint8_t status = (ts != mb_port_timestamp(Port)) ? MB_ERR_AUTH : MB_SlotErase(slot); struct __attribute__((packed)) { Header_t Header; uint8_t Slot; uint8_t Status; } Reply; Reply.Header.ID = 0x0723; Reply.Header.Size = 2; Reply.Slot = slot; Reply.Status = status; SendReply(Port, &Reply, sizeof(Reply)); break; } case 0x0724: // slot write: program bytes at slot+offset (slot pre-erased) { gSerialConfigCountDown_500ms = 12; // keep serial mode alive (6 s) uint8_t slot = pUART_Command->Data[0]; uint32_t offset = (uint32_t)pUART_Command->Data[2] | ((uint32_t)pUART_Command->Data[3] << 8) | ((uint32_t)pUART_Command->Data[4] << 16) | ((uint32_t)pUART_Command->Data[5] << 24); uint16_t len = (uint16_t)(pUART_Command->Data[6] | ((uint16_t)pUART_Command->Data[7] << 8)); uint32_t ts = (uint32_t)pUART_Command->Data[8] | ((uint32_t)pUART_Command->Data[9] << 8) | ((uint32_t)pUART_Command->Data[10] << 16) | ((uint32_t)pUART_Command->Data[11] << 24); uint8_t status; if (ts != mb_port_timestamp(Port)) status = MB_ERR_AUTH; else if (len > 240u) // 12-byte prefix + data must fit Data[252] status = MB_ERR_SIZE; else status = MB_SlotWrite(slot, offset, &pUART_Command->Data[12], len); struct __attribute__((packed)) { Header_t Header; uint8_t Slot; uint8_t Status; } Reply; Reply.Header.ID = 0x0725; Reply.Header.Size = 2; Reply.Slot = slot; Reply.Status = status; SendReply(Port, &Reply, sizeof(Reply)); break; } case 0x0726: // slot validate: full image CRC-32, no reflash { gSerialConfigCountDown_500ms = 12; // keep serial mode alive (6 s) uint8_t slot = pUART_Command->Data[0]; uint32_t crc = 0; uint8_t status = MB_ValidateSlot(slot, NULL, &crc); struct __attribute__((packed)) { Header_t Header; uint32_t Crc32; // offset 4: 4-byte aligned, no unaligned store uint8_t Slot; uint8_t Status; } Reply; Reply.Header.ID = 0x0727; Reply.Header.Size = 6; Reply.Crc32 = crc; Reply.Slot = slot; Reply.Status = status; SendReply(Port, &Reply, sizeof(Reply)); break; } case 0x0728: // profile config reset: wipe the 64 KiB config bank of a slot { gSerialConfigCountDown_500ms = 12; // keep serial mode alive (6 s) uint8_t slot = pUART_Command->Data[0]; uint32_t ts = (uint32_t)pUART_Command->Data[2] | ((uint32_t)pUART_Command->Data[3] << 8) | ((uint32_t)pUART_Command->Data[4] << 16) | ((uint32_t)pUART_Command->Data[5] << 24); uint8_t status = (ts != mb_port_timestamp(Port)) ? MB_ERR_AUTH : MB_ProfileErase(slot); struct __attribute__((packed)) { Header_t Header; uint8_t Slot; uint8_t Status; } Reply; Reply.Header.ID = 0x0729; Reply.Header.Size = 2; Reply.Slot = slot; Reply.Status = status; SendReply(Port, &Reply, sizeof(Reply)); break; } #endif #ifdef ENABLE_UART_RW_BK_REGS case 0x0601: CMD_0601_ReadBK4819Reg(Port, pUART_Command->Buffer); break; case 0x0602: CMD_0602_WriteBK4819Reg(pUART_Command->Buffer); break; #endif } // switch #ifdef ENABLE_FEAT_F4HWN_K5VIEWER gUART_LockK5Viewer = 20; // lock the K5Viewer stream #endif } void UART_ServiceCommands(void) { #ifdef ENABLE_USB if (UART_IsCommandAvailable(UART_PORT_VCP)) UART_HandleCommand(UART_PORT_VCP); #endif #ifdef ENABLE_UART if (UART_IsCommandAvailable(UART_PORT_UART)) UART_HandleCommand(UART_PORT_UART); #endif }