Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unlocking and erasing protected flash on PY32 chips #36

Open
kholia opened this issue Jan 26, 2024 · 3 comments
Open

Unlocking and erasing protected flash on PY32 chips #36

kholia opened this issue Jan 26, 2024 · 3 comments

Comments

@kholia
Copy link
Contributor

kholia commented Jan 26, 2024

Hi,

I am experimenting with flash protection on PY32 chips with the following program:

#include <string.h>
#include "main.h"
#include "py32f0xx_bsp_clock.h"
#include "py32f0xx_bsp_printf.h"

static void APP_GPIOConfig(void);

static void APP_FlashSetOptionBytes(void)
{
  FLASH_OBProgramInitTypeDef OBInitCfg;

  LL_FLASH_Unlock();
  LL_FLASH_OB_Unlock();

  OBInitCfg.OptionType = OPTIONBYTE_USER;
  OBInitCfg.USERType = OB_USER_BOR_EN | OB_USER_BOR_LEV | OB_USER_IWDG_SW | OB_USER_WWDG_SW | OB_USER_NRST_MODE | OB_USER_nBOOT1;
  OBInitCfg.USERConfig = OB_BOR_DISABLE | OB_BOR_LEVEL_3p1_3p2 | OB_IWDG_SW | OB_WWDG_SW | OB_RESET_MODE_RESET | OB_BOOT1_SYSTEM;
  LL_FLASH_OBProgram(&OBInitCfg);

  LL_FLASH_OB_RDP_LevelConfig(OB_RDP_LEVEL_1);

  LL_FLASH_Lock();
  LL_FLASH_OB_Lock();
  /* Reload option bytes */
  LL_FLASH_OB_Launch();
}

int main(void)
{
  /* Set clock = 8MHz */
  BSP_RCC_HSI_8MConfig();

  /* Enable peripheral clock */
  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA | LL_IOP_GRP1_PERIPH_GPIOB);

  BSP_USART_Config(115200);
  printf("SPI Demo: nRF24L01 Wireless\r\nClock: %ld\r\n", SystemCoreClock);

  APP_GPIOConfig();

  LL_mDelay(7000);

  uint32_t rdplvl = READ_BIT(FLASH->OPTR, FLASH_OPTR_RDP);
  if (rdplvl == OB_RDP_LEVEL_0) {
    APP_FlashSetOptionBytes();
  }
  else
  {
    printf("RESET has been configurated as RESET\r\n");
  }

  while (1)
  {
    printf("nRF24L01 check: error\r\n");
    LL_mDelay(2000);
  }
}

static void APP_GPIOConfig(void)
{
  LL_GPIO_InitTypeDef GPIO_InitStruct;

  // PA6 CSN
  LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_6, LL_GPIO_MODE_OUTPUT);
  // PA5 CE
  LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_5, LL_GPIO_MODE_OUTPUT);
  /* PA4 as input */
  GPIO_InitStruct.Pin = LL_GPIO_PIN_4;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

void APP_ErrorHandler(void)
{
  while (1);
}

#ifdef  USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
  while (1);
}
#endif /* USE_FULL_ASSERT */

After power-cycling the PY32 chip (FY32F003 in SOP-8 package), I can no longer erase the chip using J-Link.

Is there a known method to Unlock and Erase the protected flash memory on PY32 chips?

https://dzone.com/articles/unlocking-and-erasing-flash is a good read on this topic but I didn't a method which works for PY32 chips yet.

Thanks for the help.

@kholia kholia changed the title Unlocking and erasing proteced flash on PY32 chips Unlocking and erasing protected flash on PY32 chips Jan 26, 2024
@IOsetting
Copy link
Owner

In David's post he mentioned

After setting RDP1, I could no longer program or erase it with J-Link, I had to use PUYA ISP, setting BOOT=1 to enter the serial bootloader and running "Clear chip".

I guess you are in the same situation, but the problem is that SOP8(and SOP16) package has no BOOT pin so you cannot use PuyaISP to reset chip.

If JLink can still detect and connect to the chip, maybe you can use the init and exit steps to unlock and change the option bytes as described in this post

@deividAlfa
Copy link
Contributor

deividAlfa commented Feb 28, 2024

Instead using PuyaISP everytime, I added a erase function triggered by special sequence of keys.

static void rdp_unlock(void){
  const FLASH_OBProgramInitTypeDef RDP_Reset = {
      .OptionType = OPTIONBYTE_RDP | OPTIONBYTE_USER,
      .USERConfig = OB_BOR_DISABLE | OB_BOR_LEVEL_3p1_3p2| OB_IWDG_SW | OB_WWDG_SW | OB_RESET_MODE_RESET | OB_BOOT1_SYSTEM,    // Default OPT value
      .RDPLevel = OB_RDP_LEVEL_0,
  };
  LL_FLASH_Unlock();
  LL_FLASH_OB_Unlock();
  LL_FLASH_OBProgram((FLASH_OBProgramInitTypeDef*)&RDP_Reset);
  LL_FLASH_OB_Launch();
  while(1);        // We shouldn't get here
}

After LL_OB_Launch() the MCU will be instantly erased (Flash erase is forced by hardware when clearing RDP setting).
The RDP will be still active because it's only loaded at boot from the Option bytes, so it needs a power cycle to reload the new setting, then you'll be able to program it again.

@deividAlfa
Copy link
Contributor

deividAlfa commented Mar 2, 2024

I managed to brick few mcus after disabling the bootloader (OB_BOOT1_SRAM) while having a bug in the unlock sequence.
But it still can be done, as in this mode BOOT0=1 boots into SRAM.

Modify the linker script so everything is loaded into RAM: (Remove .txt extension)
py32f030x6.ld.txt

Run this code:

int main(void)
{
  WRITE_REG(FLASH->KEYR, FLASH_KEY1);
  WRITE_REG(FLASH->KEYR, FLASH_KEY2);
  WRITE_REG(FLASH->OPTKEYR, FLASH_OPTKEY1);
  WRITE_REG(FLASH->OPTKEYR, FLASH_OPTKEY2);
  FLASH->OPTR = OB_BOR_DISABLE | OB_BOR_LEVEL_3p1_3p2 | OB_IWDG_SW | OB_WWDG_SW | OB_RESET_MODE_RESET | OB_BOOT1_SYSTEM | OB_RDP_LEVEL_0;        // Default OPT value
  FLASH->CR|=FLASH_CR_OPTSTRT;
  FLASH->CR|=FLASH_CR_EOPIE;
  *((__IO uint32_t *)(0x40022080))=0xff;
  SET_BIT(FLASH->CR, FLASH_CR_OBL_LAUNCH);
  while (1){}
}

Compile and run, RDP will be cleared, forcing flash erase, and you'll recover your 11 cents back 😆

This will work in any BOOT1 mode (SYSTEM or SRAM), so it can be used to clear RDP without needing PuyaISP.
I simply made a dedicated project for this, called "Clear RDP from SRAM boot".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants