mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-19 00:37:25 +01:00
286 lines
9.9 KiB
C++
286 lines
9.9 KiB
C++
#include "dfu_interface.h"
|
|
#include <string.h>
|
|
#include <drivers/flash.h>
|
|
#include <ion/timing.h>
|
|
|
|
namespace Ion {
|
|
namespace Device {
|
|
namespace USB {
|
|
|
|
static inline uint32_t minUint32T(uint32_t x, uint32_t y) { return x < y ? x : y; }
|
|
|
|
void DFUInterface::StatusData::push(Channel * c) const {
|
|
c->push(m_bStatus);
|
|
c->push(m_bwPollTimeout[2]);
|
|
c->push(m_bwPollTimeout[1]);
|
|
c->push(m_bwPollTimeout[0]);
|
|
c->push(m_bState);
|
|
c->push(m_iString);
|
|
}
|
|
|
|
void DFUInterface::StateData::push(Channel * c) const {
|
|
c->push(m_bState);
|
|
}
|
|
|
|
void DFUInterface::wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {
|
|
if (request->bRequest() == (uint8_t) DFURequest::Download) {
|
|
// Handle a download request
|
|
if (request->wValue() == 0) {
|
|
// The request is a special command
|
|
switch (transferBuffer[0]) {
|
|
case (uint8_t) DFUDownloadCommand::SetAddressPointer:
|
|
setAddressPointerCommand(request, transferBuffer, *transferBufferLength);
|
|
return;
|
|
case (uint8_t) DFUDownloadCommand::Erase:
|
|
eraseCommand(transferBuffer, *transferBufferLength);
|
|
return;
|
|
default:
|
|
m_state = State::dfuERROR;
|
|
m_status = Status::errSTALLEDPKT;
|
|
return;
|
|
}
|
|
}
|
|
if (request->wValue() == 1) {
|
|
m_ep0->stallTransaction();
|
|
return;
|
|
}
|
|
if (request->wLength() > 0) {
|
|
// The request is a "real" download. Compute the writing address.
|
|
m_writeAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer;
|
|
// Store the received data until we copy it on the flash.
|
|
memcpy(m_largeBuffer, transferBuffer, *transferBufferLength);
|
|
m_largeBufferLength = *transferBufferLength;
|
|
m_state = State::dfuDNLOADSYNC;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DFUInterface::wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {
|
|
if (request->bRequest() == (uint8_t) DFURequest::GetStatus) {
|
|
// Do any needed action after the GetStatus request.
|
|
if (m_state == State::dfuMANIFEST) {
|
|
/* If we leave the DFU and reset immediately, dfu-util outputs an error:
|
|
* "File downloaded successfully
|
|
* dfu-util: Error during download get_status"
|
|
* If we sleep 1us here, there is no error. We put 1ms for security.
|
|
* This error might be due to the USB connection being cut too soon after
|
|
* the last USB exchange, so the host does not have time to process the
|
|
* answer received for the last GetStatus request. */
|
|
Ion::Timing::msleep(1);
|
|
// Leave DFU routine: Leave DFU, reset device, jump to application code
|
|
leaveDFUAndReset();
|
|
} else if (m_state == State::dfuDNBUSY) {
|
|
if (m_largeBufferLength != 0) {
|
|
// Here, copy the data from the transfer buffer to the flash memory
|
|
writeOnMemory();
|
|
}
|
|
changeAddressPointerIfNeeded();
|
|
eraseMemoryIfNeeded();
|
|
m_state = State::dfuDNLOADIDLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool DFUInterface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) {
|
|
if (Interface::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) {
|
|
return true;
|
|
}
|
|
switch (request->bRequest()) {
|
|
case (uint8_t) DFURequest::Detach:
|
|
m_device->detach();
|
|
return true;
|
|
case (uint8_t) DFURequest::Download:
|
|
return processDownloadRequest(request->wLength(), transferBufferLength);
|
|
case (uint8_t) DFURequest::Upload:
|
|
return processUploadRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength);
|
|
case (uint8_t) DFURequest::GetStatus:
|
|
return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength);
|
|
case (uint8_t) DFURequest::ClearStatus:
|
|
return clearStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength);
|
|
case (uint8_t) DFURequest::GetState:
|
|
return getState(transferBuffer, transferBufferLength, transferBufferMaxLength);
|
|
case (uint8_t) DFURequest::Abort:
|
|
return dfuAbort(transferBufferLength);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength) {
|
|
if (m_state != State::dfuIDLE && m_state != State::dfuDNLOADIDLE) {
|
|
m_state = State::dfuERROR;
|
|
m_status = Status::errNOTDONE;
|
|
m_ep0->stallTransaction();
|
|
return false;
|
|
}
|
|
if (wLength == 0) {
|
|
// Leave DFU routine: Reset the device and jump to application code
|
|
m_state = State::dfuMANIFESTSYNC;
|
|
} else {
|
|
// Prepare to receive the download data
|
|
m_ep0->clearForOutTransactions(wLength);
|
|
m_state = State::dfuDNLOADSYNC;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DFUInterface::processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) {
|
|
if (m_state != State::dfuIDLE && m_state != State::dfuUPLOADIDLE) {
|
|
m_ep0->stallTransaction();
|
|
return false;
|
|
}
|
|
if (request->wValue() == 0) {
|
|
/* The host requests to read the commands supported by the bootloader. After
|
|
* receiving this command, the device should returns N bytes representing
|
|
* the command codes for :
|
|
* Get command / Set Address Pointer / Erase / Read Unprotect
|
|
* We no not need it for now. */
|
|
return false;
|
|
} else if (request->wValue() == 1) {
|
|
m_ep0->stallTransaction();
|
|
return false;
|
|
} else {
|
|
/* We decided to never protect Read operation. Else we would have to check
|
|
* here it is not protected before reading. */
|
|
|
|
// Compute the reading address
|
|
uint32_t readAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer;
|
|
// Copy the requested memory zone into the transfer buffer.
|
|
uint16_t copySize = minUint32T(transferBufferMaxLength, request->wLength());
|
|
memcpy(transferBuffer, (void *)readAddress, copySize);
|
|
*transferBufferLength = copySize;
|
|
}
|
|
m_state = State::dfuUPLOADIDLE;
|
|
return true;
|
|
}
|
|
|
|
void DFUInterface::setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength) {
|
|
assert(transferBufferLength == 5);
|
|
// Compute the new address but change it after the next getStatus request.
|
|
m_potentialNewAddressPointer = transferBuffer[1]
|
|
+ (transferBuffer[2] << 8)
|
|
+ (transferBuffer[3] << 16)
|
|
+ (transferBuffer[4] << 24);
|
|
m_state = State::dfuDNLOADSYNC;
|
|
}
|
|
|
|
void DFUInterface::changeAddressPointerIfNeeded() {
|
|
if (m_potentialNewAddressPointer == 0) {
|
|
// There was no address change waiting.
|
|
return;
|
|
}
|
|
// If there is a new address pointer waiting, change the pointer address.
|
|
m_addressPointer = m_potentialNewAddressPointer;
|
|
m_potentialNewAddressPointer = 0;
|
|
m_state = State::dfuDNLOADIDLE;
|
|
m_status = Status::OK;
|
|
}
|
|
|
|
void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength) {
|
|
/* We determine whether the commands asks for a mass erase or which sector to
|
|
* erase. The erase must be done after the next getStatus request. */
|
|
m_state = State::dfuDNLOADSYNC;
|
|
|
|
if (transferBufferLength == 1) {
|
|
// Mass erase
|
|
m_erasePage = Flash::TotalNumberOfSectors();
|
|
return;
|
|
}
|
|
|
|
// Sector erase
|
|
assert(transferBufferLength == 5);
|
|
|
|
uint32_t eraseAddress = transferBuffer[1]
|
|
+ (transferBuffer[2] << 8)
|
|
+ (transferBuffer[3] << 16)
|
|
+ (transferBuffer[4] << 24);
|
|
|
|
m_erasePage = Flash::SectorAtAddress(eraseAddress);
|
|
if (m_erasePage < 0) {
|
|
// Unrecognized sector
|
|
m_state = State::dfuERROR;
|
|
m_status = Status::errTARGET;
|
|
}
|
|
}
|
|
|
|
|
|
void DFUInterface::eraseMemoryIfNeeded() {
|
|
if (m_erasePage < 0) {
|
|
// There was no erase waiting.
|
|
return;
|
|
}
|
|
|
|
if (m_erasePage == Flash::TotalNumberOfSectors()) {
|
|
Flash::MassErase();
|
|
} else {
|
|
Flash::EraseSector(m_erasePage);
|
|
}
|
|
|
|
/* Put an out of range value in m_erasePage to indicate that no erase is
|
|
* waiting. */
|
|
m_erasePage = -1;
|
|
m_state = State::dfuDNLOADIDLE;
|
|
m_status = Status::OK;
|
|
}
|
|
|
|
void DFUInterface::writeOnMemory() {
|
|
if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) {
|
|
// Write on SRAM
|
|
// FIXME We should check that we are not overriding the current instructions.
|
|
memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength);
|
|
} else if (Flash::SectorAtAddress(m_writeAddress) >= 0) {
|
|
Flash::WriteMemory(reinterpret_cast<uint8_t *>(m_writeAddress), m_largeBuffer, m_largeBufferLength);
|
|
} else {
|
|
// Invalid write address
|
|
m_largeBufferLength = 0;
|
|
m_state = State::dfuERROR;
|
|
m_status = Status::errTARGET;
|
|
return;
|
|
}
|
|
|
|
// Reset the buffer length
|
|
m_largeBufferLength = 0;
|
|
// Change the interface state and status
|
|
m_state = State::dfuDNLOADIDLE;
|
|
m_status = Status::OK;
|
|
}
|
|
|
|
|
|
bool DFUInterface::getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) {
|
|
// Change the status if needed
|
|
if (m_state == State::dfuMANIFESTSYNC) {
|
|
m_state = State::dfuMANIFEST;
|
|
} else if (m_state == State::dfuDNLOADSYNC) {
|
|
m_state = State::dfuDNBUSY;
|
|
}
|
|
// Copy the status on the TxFifo
|
|
*transferBufferLength = StatusData(m_status, m_state).copy(transferBuffer, transferBufferMaxLength);
|
|
return true;
|
|
}
|
|
|
|
bool DFUInterface::clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) {
|
|
m_status = Status::OK;
|
|
m_state = State::dfuIDLE;
|
|
return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength);
|
|
}
|
|
|
|
bool DFUInterface::getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize) {
|
|
*transferBufferLength = StateData(m_state).copy(transferBuffer, maxSize);
|
|
return true;
|
|
}
|
|
|
|
bool DFUInterface::dfuAbort(uint16_t * transferBufferLength) {
|
|
m_status = Status::OK;
|
|
m_state = State::dfuIDLE;
|
|
*transferBufferLength = 0;
|
|
return true;
|
|
}
|
|
|
|
void DFUInterface::leaveDFUAndReset() {
|
|
m_device->setResetOnDisconnect(true);
|
|
m_device->detach();
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|