diff --git a/apps/settings/sub_menu/usb_protection_controller.cpp b/apps/settings/sub_menu/usb_protection_controller.cpp index f551f6121..07eddc743 100644 --- a/apps/settings/sub_menu/usb_protection_controller.cpp +++ b/apps/settings/sub_menu/usb_protection_controller.cpp @@ -13,7 +13,7 @@ using namespace Shared; namespace Settings { -UsbInfoController::UsbInfoController(Responder *parentResponder): +UsbInfoController::UsbInfoController(Responder *parentResponder): GenericSubController(parentResponder), m_usbProtectionLevelController(this), m_contentView(&m_selectableTableView) @@ -23,7 +23,7 @@ UsbInfoController::UsbInfoController(Responder *parentResponder): } bool UsbInfoController::handleEvent(Ion::Events::Event event) { - if ((Ion::Events::OK == event || Ion::Events::EXE == event) && selectedRow() == 0) { + if ((Ion::Events::OK == event || Ion::Events::EXE == event || Ion::Events::Right == event) && selectedRow() == 0) { if (!GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked()) { if (!GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { Ion::LED::setColor(KDColorPurple); @@ -39,15 +39,8 @@ bool UsbInfoController::handleEvent(Ion::Events::Event event) { m_selectableTableView.reloadCellAtLocation(0, 0); return true; } - // We cannot use things like willExitResponderChain because this view can disappear due to an USB connection, - // and in this case we must keep the DFU status. - if ((Ion::Events::Left == event || Ion::Events::Home == event || Ion::Events::Back == event) && GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked()) { - GlobalPreferences::sharedGlobalPreferences()->setDfuUnlocked(false); - m_selectableTableView.reloadCellAtLocation(0, 0); - Container::activeApp()->displayWarning(I18n::Message::USBProtectionReactivated); - return true; - } - if ((Ion::Events::OK == event || Ion::Events::EXE == event) && selectedRow() == 1) { + + if ((Ion::Events::OK == event || Ion::Events::EXE == event || Ion::Events::Right == event) && selectedRow() == 1) { GenericSubController *subController = &m_usbProtectionLevelController; subController->setMessageTreeModel(m_messageTreeModel->childAtIndex(1)); StackViewController *stack = stackController(); @@ -55,6 +48,20 @@ bool UsbInfoController::handleEvent(Ion::Events::Event event) { stack->push(subController); return true; } + + // We cannot use things like willExitResponderChain because this view can disappear due to an USB connection, + // and in this case we must keep the DFU status. + if ((event != Ion::Events::USBPlug && event != Ion::Events::USBEnumeration) && + GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked()) { + GlobalPreferences::sharedGlobalPreferences()->setDfuUnlocked(false); + m_selectableTableView.reloadCellAtLocation(0, 0); + Container::activeApp()->displayWarning(I18n::Message::USBProtectionReactivated); + if (!GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { + Ion::LED::setColor(KDColorBlack); + } + return true; + } + return GenericSubController::handleEvent(event); } diff --git a/build/device/dfu.py b/build/device/dfu.py index bac05e3e5..47358c53d 100644 --- a/build/device/dfu.py +++ b/build/device/dfu.py @@ -47,6 +47,7 @@ __DFU_GETSTATUS = 3 __DFU_CLRSTATUS = 4 __DFU_GETSTATE = 5 __DFU_ABORT = 6 +__DFU_UNLOCK = 11 # DFU status __DFU_STATE_APP_IDLE = 0x00 @@ -145,6 +146,10 @@ def abort_request(): __dev.ctrl_transfer(0x21, __DFU_ABORT, 0, __DFU_INTERFACE, None, __TIMEOUT) +def unlock_request(): + """Deactivate the protection""" + __dev.ctrl_transfer(0x21, __DFU_UNLOCK, 0, __DFU_INTERFACE, None, __TIMEOUT) + def clr_status(): """Clears any error status (perhaps left over from a previous session).""" __dev.ctrl_transfer(0x21, __DFU_CLRSTATUS, 0, __DFU_INTERFACE, @@ -587,6 +592,7 @@ def main(): elements = read_dfu_file(args.path) if not elements: return + unlock_request() print("Writing memory...") write_elements(elements, args.mass_erase, progress=cli_progress) diff --git a/ion/src/device/n0110/flash.ld b/ion/src/device/n0110/flash.ld index 1f2ebb8bb..4bdf639f5 100644 --- a/ion/src/device/n0110/flash.ld +++ b/ion/src/device/n0110/flash.ld @@ -176,8 +176,8 @@ SECTIONS { *(.text._ZN20KDPostProcessContext15setClippingRectE6KDRect) *(.text._ZNK6KDFont17indexForCodePointE9CodePoint) *(.text._ZNK6KDFont26fetchGrayscaleGlyphAtIndexEhPh) - *(.text.LZ4_decompress_safe) - *(.text.LZ4_wildCopy) + *(.text.LZ4_decompress_safe*) + *(.text.LZ4_wildCopy*) *(.text.*DFU*) *(.text.*isEnumerated*) *(.text._ZN3Ion3USB6enableEv) @@ -214,6 +214,7 @@ SECTIONS { *(.text._ZNK10Statistics5Store33sortedElementAtCumulatedFrequencyEidb) *(.text.round) *(.text._ZNK10Statistics5Store8minIndexEPdi*) + *(.text.LZ4_decompress_safe*) /* 'standby' dependencies '*/ *(.text._ZN3Ion6Device5Power20internalFlashStandbyEv) @@ -277,6 +278,12 @@ SECTIONS { *(.rodata._ZN8Sequence23TypeParameterController25willDisplayCellAtLocationEP13HighlightCellii*) *(.rodata._ZN6KDFont16privateSmallFontE) *(.rodata._ZN4I18nL23CountryPreferencesArrayE) + *(.rodata._ZN3Ion6Device3LED6ConfigL7RGBPinsE*) + *(.rodata._ZN4I18nL23CountryPreferencesArrayE*) + *(.rodata._ZN3Ion6Device3USB6ConfigL7VbusPinE*) + *(.rodata.bp*) + *(.rodata.dp_l*) + *(.rodata.dp_h*) *(.rodata.abort_sleeping.str1.1) *(.rodata.abort_core.str1.1) *(.rodata.dfu_bootloader) diff --git a/ion/src/device/shared/usb/dfu_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp index 6a7a02441..a473e1422 100644 --- a/ion/src/device/shared/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -15,8 +15,6 @@ namespace Ion { namespace Device { namespace USB { -constexpr static uint8_t k_externalMagik[9] = {0x64, 0x6c, 0x31, 0x31, 0x23, 0x39, 0x38, 0x33, 0x35}; - static inline uint32_t minUint32T(uint32_t x, uint32_t y) { return x < y ? x : y; } void DFUInterface::StatusData::push(Channel *c) const { @@ -111,6 +109,9 @@ bool DFUInterface::processSetupInRequest(SetupPacket *request, uint8_t *transfer return getState(transferBuffer, transferBufferLength, transferBufferMaxLength); case (uint8_t)DFURequest::Abort: return dfuAbort(transferBufferLength); + case (uint8_t)DFURequest::Unlock: + m_dfuUnlocked = true; + return true; } return false; } @@ -140,17 +141,17 @@ bool DFUInterface::processUploadRequest(SetupPacket *request, uint8_t *transferB } 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. */ + * 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. */ + * here it is not protected before reading. */ // Compute the reading address uint32_t readAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; @@ -199,7 +200,7 @@ void DFUInterface::eraseCommand(uint8_t *transferBuffer, uint16_t transferBuffer // Sector erase assert(transferBufferLength == 5); - uint32_t m_eraseAddress = transferBuffer[1] + m_eraseAddress = transferBuffer[1] + (transferBuffer[2] << 8) + (transferBuffer[3] << 16) + (transferBuffer[4] << 24); @@ -214,24 +215,21 @@ void DFUInterface::eraseCommand(uint8_t *transferBuffer, uint16_t transferBuffer void DFUInterface::eraseMemoryIfNeeded() { if (m_erasePage < 0) { - // There was no erase waiting. return; } willErase(); -#if 0 // We don't erase now the flash memory to avoid crash if writing is refused + #if 0 // We don't erase now the flash memory to avoid crash if writing is refused if (m_erasePage == Flash::TotalNumberOfSectors()) { - Flash::MassErase(); + Flash::MassErase(); } -#endif + #endif - if ((m_eraseAddress >= k_externalAppsBorderAddress && m_eraseAddress < ExternalFlash::Config::EndAddress) || m_dfuUnlocked) { - Flash::EraseSector(m_erasePage); + if ((m_eraseAddress >= k_ExternalBorderAddress && m_eraseAddress < ExternalFlash::Config::EndAddress) || m_dfuUnlocked) { + int32_t order = Flash::SectorAtAddress(m_eraseAddress); + Flash::EraseSector(order); } - /* 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; m_erasePage = -1; @@ -242,67 +240,89 @@ void DFUInterface::writeOnMemory() { // Write on SRAM // FIXME We should check that we are not overriding the current instructions. memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); - resetProtectionVariables(); // We can reset the protection variables because update process is finsihed. + resetFlashParameters(); // We are writing in SRAM, so we can reset flash parameters } else if (Flash::SectorAtAddress(m_writeAddress) >= 0) { - if (m_dfuLevel == 2) { // If no-update mode, we throw an error + if (m_dfuLevel == 2) { // We don't accept update m_largeBufferLength = 0; m_state = State::dfuERROR; m_status = Status::errWRITE; - leaveDFUAndReset(false); return; } - - if (!(m_isTemporaryUnlocked || m_dfuUnlocked)) { - if (m_writeAddress >= InternalFlash::Config::StartAddress && m_writeAddress <= InternalFlash::Config::EndAddress) { - // We check if the user is autorized to write on the internal flash - if (m_haveAlreadyFlashedExternal) { - for (size_t i = 0; i < 4; i++) { - if (k_internalMagik[i] != m_largeBuffer[k_internalMagikPointer + i]) { - m_largeBufferLength = 0; - m_state = State::dfuERROR; - m_status = Status::errWRITE; - // We don't leave DFU to avoid having only external flashed - return; - } - m_largeBuffer[k_internalMagikPointer + i] = 0; // We reset the buffer to its initial value + + int currentMemoryType; // Detection of the current memory type (Internal or External) + + if (m_writeAddress >= InternalFlash::Config::StartAddress && m_writeAddress <= InternalFlash::Config::EndAddress) { + // We are writing in Internal where live the internal recovery (it's the most sensitive memory type) + if (m_isInternalLocked && !m_dfuUnlocked) { + // We have to check if external was written in order to + // prevent recovery mode loop or the necessity to activate STM bootloader (which is like a superuser mode) + // Nevertheless, unlike NumWorks, we don't forbid its access. + m_largeBufferLength = 0; + m_state = State::dfuERROR; + m_status = Status::errTARGET; + leaveDFUAndReset(false); + return; + } + + currentMemoryType = 0; + + // If the protection is activated, + // we check the internal magic code in order to prevent the NumWorks' Bootloader flash + + if (m_isFirstInternalPacket && !m_dfuUnlocked) { + for (int i = 0; i < 4; i++) { + if (k_omegaMagic[i] != m_largeBuffer[k_internalMagicAddress + i]) { + m_largeBufferLength = 0; + m_state = State::dfuERROR; + m_status = Status::errVERIFY; + return; } } - else { // All people trying to write on the internal flash before external are considered as not authorized - m_largeBufferLength = 0; - m_state = State::dfuERROR; - m_status = Status::errTARGET; - leaveDFUAndReset(false); - return; - } + // We only check the first packet because there is some predictable data in there + m_isFirstInternalPacket = false; } - else if (m_writeAddress < k_externalAppsBorderAddress) { // If we are not installing external apps - if (m_dfuLevel == 0) { - for (size_t i = 0; i < 9; i++) { - if (k_externalMagik[i] != m_largeBuffer[k_externalMagikPointer + i]) { - m_largeBufferLength = 0; - m_state = State::dfuERROR; - m_status = Status::errWRITE; - leaveDFUAndReset(false); - return; - } - m_largeBuffer[k_externalMagikPointer + i] = 0; // We reset the buffer to its initial value + } else { + + currentMemoryType = 1; + // We are writing in the external part where live the users apps. It's not a sensitive memory, + // but we check it in Upsilon Mode to ensure compatibility between the internal and the external. + if (m_writeAddress < k_ExternalBorderAddress && m_isFirstExternalPacket && m_dfuLevel == 0 && + !m_dfuUnlocked) { + // We skip any data verification if the user is writing in the Optionals Applications part in the + // external (Externals Apps) + for (int i = 0; i < 4; i++) { + if (k_externalUpsilonMagic[i] != m_largeBuffer[k_externalMagicAddress + i]) { + m_largeBufferLength = 0; + leaveDFUAndReset(false); + return; } - m_isTemporaryUnlocked = true; // We can unlock the flash because signature is good - } - else { - m_haveAlreadyFlashedExternal = true; } } + // We only check the first packet because there is some predictable data in there, + // and we unlock the internal memory + m_isFirstExternalPacket = false; + m_isInternalLocked = false; } - int pageToErase = Flash::SectorAtAddress(m_writeAddress); - - //On vérifie qu'on a pas déjà effacé le secteur et si ce n'est pas un secteur external déjà effacé - if ((m_lastErasedPage == -1 || pageToErase != m_lastErasedPage) && m_writeAddress < k_externalAppsBorderAddress && !m_dfuUnlocked) { - Flash::EraseSector(pageToErase); - m_lastErasedPage = pageToErase; + // We check if we changed the memory type where we are writing from last time. + if (m_lastMemoryType >= 0 && currentMemoryType != m_lastMemoryType) { + m_lastMemoryType = -1; } + m_erasePage = Flash::SectorAtAddress(m_writeAddress); + + // We check if the Sector where we are writing was not already erased and if not, we erase it. + if ((m_lastMemoryType < 0 || m_erasePage != m_lastPageErased) && + m_writeAddress < k_ExternalBorderAddress && !m_dfuUnlocked) { + Flash::EraseSector(m_erasePage); + m_lastMemoryType = currentMemoryType; + } + + m_lastPageErased = m_erasePage; + m_erasePage = -1; + + // We wait a little before writing in order to prevent some memory error. + Ion::Timing::msleep(1); Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); } else { // Invalid write address @@ -311,7 +331,6 @@ void DFUInterface::writeOnMemory() { m_status = Status::errTARGET; return; } - // Reset the buffer length m_largeBufferLength = 0; // Change the interface state and status @@ -350,11 +369,14 @@ bool DFUInterface::dfuAbort(uint16_t *transferBufferLength) { } void DFUInterface::leaveDFUAndReset(bool do_reset) { - resetProtectionVariables(); + resetFlashParameters(); + m_isInternalLocked = true; + m_isFirstInternalPacket = true; + m_isFirstExternalPacket = true; m_device->setResetOnDisconnect(do_reset); m_device->detach(); } -} +} } } diff --git a/ion/src/device/shared/usb/dfu_interface.h b/ion/src/device/shared/usb/dfu_interface.h index dd87c6cdd..f05604e77 100644 --- a/ion/src/device/shared/usb/dfu_interface.h +++ b/ion/src/device/shared/usb/dfu_interface.h @@ -21,7 +21,7 @@ namespace USB class DFUInterface : public Interface { public: - DFUInterface(Device *device, Endpoint0 *ep0, uint8_t bInterfaceAlternateSetting): + DFUInterface(Device *device, Endpoint0 *ep0, uint8_t bInterfaceAlternateSetting) : Interface(ep0), m_device(device), m_status(Status::OK), @@ -34,16 +34,18 @@ public: m_writeAddress(0), m_eraseAddress(0), m_bInterfaceAlternateSetting(bInterfaceAlternateSetting), - m_lastErasedPage(-1), m_isErasingAndWriting(false), - m_isTemporaryUnlocked(false), - m_haveAlreadyFlashedExternal(false), + m_isFirstInternalPacket(true), + m_isInternalLocked(true), + m_isFirstExternalPacket(true), + m_lastMemoryType(-1), + m_lastPageErased(-1), m_dfuUnlocked(false), - m_dfuLevel(0) - { - + m_dfuLevel(0) { } + uint32_t addressPointer() const { return m_addressPointer; } + void wholeDataReceivedCallback(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength) override; void wholeDataSentCallback(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength) override; bool isErasingAndWriting() const { return m_isErasingAndWriting; } @@ -68,7 +70,8 @@ private: GetStatus = 3, ClearStatus = 4, GetState = 5, - Abort = 6 + Abort = 6, + Unlock = 11 }; // DFU Download Commmand Codes @@ -99,32 +102,33 @@ private: }; enum class State : uint8_t { - appIDLE = 0, - appDETACH = 1, - dfuIDLE = 2, - dfuDNLOADSYNC = 3, - dfuDNBUSY = 4, - dfuDNLOADIDLE = 5, - dfuMANIFESTSYNC = 6, - dfuMANIFEST = 7, + appIDLE = 0, + appDETACH = 1, + dfuIDLE = 2, + dfuDNLOADSYNC = 3, + dfuDNBUSY = 4, + dfuDNLOADIDLE = 5, + dfuMANIFESTSYNC = 6, + dfuMANIFEST = 7, dfuMANIFESTWAITRESET = 8, - dfuUPLOADIDLE = 9, - dfuERROR = 10 + dfuUPLOADIDLE = 9, + dfuERROR = 10 }; class StatusData : public Streamable { public: - StatusData(Status status, State state, uint32_t pollTimeout = 10): + StatusData(Status status, State state, uint32_t pollTimeout = 10) : /* We put a default pollTimeout value of 1ms: if the device is busy, the - * host has to wait 1ms before sending a getStatus Request. */ - m_bStatus((uint8_t)status), - m_bwPollTimeout{uint8_t((pollTimeout >> 16) & 0xFF), uint8_t((pollTimeout >> 8) & 0xFF), uint8_t(pollTimeout & 0xFF)}, - m_bState((uint8_t)state), + * host has to wait 1ms before sending a getStatus Request. */ + m_bStatus((uint8_t) status), + m_bwPollTimeout{uint8_t((pollTimeout >> 16) & 0xFF), uint8_t((pollTimeout >> 8) & 0xFF),uint8_t(pollTimeout & 0xFF)}, + m_bState((uint8_t) state), m_iString(0) - { - } + { + } + protected: - void push(Channel * c) const override; + void push(Channel *c) const override; private: uint8_t m_bStatus; // Status resulting from the execution of the most recent request uint8_t m_bwPollTimeout[3]; // m_bwPollTimeout is 24 bits @@ -135,7 +139,7 @@ private: class StateData : public Streamable { public: - StateData(State state) : m_bState((uint8_t)state) {} + StateData(State state) : m_bState((uint8_t) state) {} protected: void push(Channel *c) const override; private: @@ -143,15 +147,16 @@ private: }; /* The Flash and SRAM addresses are in flash.ld. However, dfu_interface is - * linked with dfu.ld, so we cannot access the values. */ - constexpr static uint32_t k_sramStartAddress = 0x20000000; - constexpr static uint32_t k_sramEndAddress = 0x20040000; - constexpr static uint32_t k_externalAppsBorderAddress = 0x90200000; +* linked with dfu.ld, so we cannot access the values. */ + constexpr static uint32_t k_sramStartAddress = 0x20000000; + constexpr static uint32_t k_sramEndAddress = 0x20040000; + constexpr static uint32_t k_ExternalBorderAddress = 0x90200000; - constexpr static int k_internalMagikPointer = 0x1C4; - constexpr static int k_externalMagikPointer = 0x44F; - constexpr static uint8_t k_internalMagik[4] = {0xF0, 0x0D, 0xC0, 0xDE}; - constexpr static uint8_t k_externalMagik[4] = {0x32, 0x30, 0x30, 0x36}; + const static int k_internalMagicAddress = 0x1C4; + constexpr static int k_externalMagicAddress = 0x44f; + constexpr static uint8_t k_omegaMagic[4] = {0xF0, 0x0D, 0xC0, 0xDE}; + // TODO maybe do: add seperated upsilon magic (k_upsilonMagic) + constexpr static uint8_t k_externalUpsilonMagic[4] = {0x32, 0x30, 0x30, 0x36}; // Download and upload bool processDownloadRequest(uint16_t wLength, uint16_t *transferBufferLength); @@ -174,14 +179,18 @@ private: // Abort bool dfuAbort(uint16_t *transferBufferLength); // Leave DFU - void leaveDFUAndReset(bool do_reset=true); + void leaveDFUAndReset(bool do_reset = true); + /* Erase and Write state. After starting the erase of flash memory, the user * can no longer leave DFU mode by pressing the Back key of the keyboard. This * way, we prevent the user from interrupting a software download. After every * software download, the calculator resets, which unlocks the "exit on * pressing back". */ void willErase() { m_isErasingAndWriting = true; } - void resetProtectionVariables() { m_lastErasedPage = -1; m_isTemporaryUnlocked = false; m_haveAlreadyFlashedExternal = false; } + void resetFlashParameters() { + m_lastMemoryType = -1; + m_lastPageErased = -1; + } Device *m_device; Status m_status; @@ -194,10 +203,12 @@ private: uint32_t m_writeAddress; uint32_t m_eraseAddress; uint8_t m_bInterfaceAlternateSetting; - uint8_t m_lastErasedPage; bool m_isErasingAndWriting; - bool m_isTemporaryUnlocked; - bool m_haveAlreadyFlashedExternal; + bool m_isFirstInternalPacket; + bool m_isInternalLocked; + bool m_isFirstExternalPacket; + uint8_t m_lastMemoryType; // -1: None; 0: internal; 1: external + uint8_t m_lastPageErased; // -1 default value bool m_dfuUnlocked; uint8_t m_dfuLevel; // 0: Upsilon only, 1: Omega-forked only, 2: No update }; diff --git a/ion/src/shared/platform_info.cpp b/ion/src/shared/platform_info.cpp index 1950cf09e..2167f528f 100644 --- a/ion/src/shared/platform_info.cpp +++ b/ion/src/shared/platform_info.cpp @@ -38,6 +38,7 @@ public: #else m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, #endif + m_osType(UpsilonMagic), m_ohm_footer(OmegaMagic) { } const char * version() const { assert(m_storageAddress != nullptr); @@ -78,6 +79,7 @@ public: private: constexpr static uint32_t Magic = 0xDEC00DF0; constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; uint32_t m_header; const char m_version[8]; const char m_patchLevel[8]; @@ -87,6 +89,7 @@ private: uint32_t m_ohm_header; const char m_UpsilonVersion[16]; const volatile char m_username[16]; + uint32_t m_osType; uint32_t m_ohm_footer; };