#include "usb_com.h" #include "usb_util.h" #include "commands.h" #include "buildinfo.h" #include "flash.h" static const BootloaderInfo_t BootloaderInfo = { .build_date = BUILD_DATE, .build_number = BUILD_NUMBER, .flash_application_start = FLASH_APPLICATION_BASE, .flash_application_size = (FLASH_PAGES - FLASH_BOOTLOADER_PAGES) * FLASH_PAGE_BYTES, .version_major = BUILD_VERSION_MAJOR, .version_minor = BUILD_VERSION_MINOR, .version_patch = BUILD_VERSION_PATCH, .identifier = "STM32F103T8U6" }; static Command_t USB_PendingCommand = CMD_NOP; static void USB_EP1Transmit(const void *data, uint16_t length) { USB_MemoryToPMA(USB_BTABLE_ENTRIES[1].ADDR_TX, data, length); USB_BTABLE_ENTRIES[1].COUNT_TX = length; // Assume that STAT_TX is currently NAK (which is the case after a reset or // after a correct transfer, just not if the function is called multiple // times without an actual transfer in between) USB->EP1R = (USB_EP_TX_NAK ^ USB_EP_TX_VALID) | USB_EP_CTR_RX | USB_EP_CTR_TX | USB_EPR_EP_TYPE_BULK | 1; } bool USB_HandleCommand(const USB_SetupPacket_t *sp) { // The command is stored in the second byte (bRequest field) of the setup // packet Command_t command = sp->bRequest; const void *reply_data = NULL; int reply_length = 0; switch(command) { case CMD_BOOTLOADER_INFO: reply_data = &BootloaderInfo; reply_length = sizeof(BootloaderInfo) + strlen(BootloaderInfo.identifier); break; case CMD_READ_CRC: // The command will be executed as soon as the start address and // length are transferred via EP2 USB_PendingCommand = CMD_READ_CRC; break; case CMD_READ_MEMORY: // The command will be executed as soon as the start address and // length are transferred via EP2 USB_PendingCommand = CMD_READ_MEMORY; break; case CMD_ERASE_PAGE: // The command will be executed as soon as the page number is // transferred via EP2. Since only a single byte is needed for the // page index, this would also be technically be possible with just // one setup packet. Since this is the only command, this minor // USB bandwith saving is not worth the extra special case. USB_PendingCommand = CMD_ERASE_PAGE; break; case CMD_PROGRAM: USB_PendingCommand = CMD_PROGRAM; break; case CMD_EXIT: return false; default: // Invalid commands get ignored break; } if(reply_length > 0) { USB_EP1Transmit(reply_data, reply_length); } return true; } void USB_HandleEP2Out(void) { // Read how many bytes have been received by EP2 int packet_length = USB_BTABLE_ENTRIES[2].COUNT_RX & 0x3ff; // The meaning of the received data depends on the command transmitted via // a setup packet before it switch(USB_PendingCommand) { case CMD_READ_CRC: if(packet_length == 8) { uint32_t buff[2]; USB_PMAToMemory(buff, USB_BTABLE_ENTRIES[2].ADDR_RX, sizeof(buff)); uint32_t *addr = (uint32_t*)(buff[0]); uint32_t length = buff[1]; CRC->CR = CRC_CR_RESET; // TODO: Add basic sanity checks so it isn't possible to crash // the bootloader with this command (or at least not as easy) while(length > 4) { CRC->DR = *addr++; length -= 4; } CRC->DR = *addr & (0xffffffffU >> (32 - 8 * length)); buff[0] = CRC->DR; USB_EP1Transmit(buff, 4); } break; case CMD_READ_MEMORY: if(packet_length == 8) { uint32_t buff[2]; USB_PMAToMemory(buff, USB_BTABLE_ENTRIES[2].ADDR_RX, sizeof(buff)); uint8_t *start = (uint8_t*)(buff[0]); uint32_t length = buff[1]; if(length > 64) { length = 64; } USB_EP1Transmit(start, length); } break; case CMD_ERASE_PAGE: if(packet_length == 1) { // Not that only one byte has been received but since the PMA // can only be accessed word-wise, we'll have to copy two uint8_t buff[2]; USB_PMAToMemory(buff, USB_BTABLE_ENTRIES[2].ADDR_RX, sizeof(buff)); unsigned int page_index = buff[0]; if(page_index < FLASH_BOOTLOADER_PAGES) { // Do not allow erasing the bootloader buff[0] = FLASH_PROHIBITED; } else { buff[0] = Flash_ErasePage(page_index); } // Reply with status byte USB_EP1Transmit(buff, 1); } break; case CMD_PROGRAM:; uint32_t start; USB_PMAToMemory(&start, USB_BTABLE_ENTRIES[2].ADDR_RX, 4); uint32_t length = packet_length - 4; if(start >= FLASH_APPLICATION_BASE && start + length <= FLASH_BASE + FLASH_PAGE_BYTES * FLASH_PAGES) { // Program directly from PMA without an intermediate buffer in // RAM Flash_ProgramFromPMA(start, USB_BTABLE_ENTRIES[2].ADDR_RX + 4, length); } break; default: break; } USB_PendingCommand = CMD_NOP; }