Skip to content

STM8 EEPROM unlocking bug

Sarun Rattanasiri edited this page Dec 4, 2018 · 3 revisions

I just found a hardware bug in STM8 CPU which cause instability of EEPROM write operations. For those who wander around looking for clues, I hope you will find the answer here.

Symptom of the bug

You try enabling write access to the EEPROM on STM8 microcontrollers and carefully follow the instructions in the reference manual from STMicroelectronics. However, you can't reliably get the result you want. Sometimes it works sometimes it doesn't.

Retrace what potentially brings you here

You read the reference manual from STMicroelectronics which looks like this:
Enabling write access to the DATA area After a device reset, it is possible to disable the DATA area write protection by writing consecutively two values called MASS keys to the FLASH_DUKR register. These programmed keys are then compared to two hardware key values First hardware key: 0b1010 1110 (0xAE) Second hardware key: 0b0101 0110 (0x56) The following steps are required to disable write protection of the DATA area: 1. Write a first 8-bit key into the FLASH_DUKR register. When this register is written for the first time after a reset, the data bus content is not latched into the register, but compared to the first hardware key value (0xAE). 2. If the key available on the data bus is incorrect, the application can re-enter two MASS keys to try unprotecting the DATA area. 3. If the first hardware key is correct, the FLASH_DUKR register is programmed with the second key. The data bus content is still not latched into the register, but compared to the second hardware key value (0x56). 4. If the key available on the data bus is incorrect, the data EEPROM area remains write protected until the next reset. Any new write command sent to this address is ignored. 5. If the second hardware key is correct, the DATA area is write unprotected and the DUL bit of the FLASH_IAPSR register is set. Before starting programming, the application must verify that the DATA area is not write protected by checking that the DUL bit is effectively set. The application can choose, at any time, to disable again write access to the DATA area by clearing the DUL bit.
And come up with what majority of people would
like Mark Stevens

if (FLASH_IAPSR_DUL == 0) {
FLASH_DUKR = 0xae;
FLASH_DUKR = 0x56;
}

// write to EEPROM

FLASH_IAPSR_DUL = 0;

or the standard peripheral driver released by STMicroelectronics themselves

void FLASH_Unlock(FLASH_MemType_TypeDef FLASH_MemType)
{
  /* Check parameter */
  assert_param(IS_MEMORY_TYPE_OK(FLASH_MemType));
  
  /* Unlock program memory */
  if(FLASH_MemType == FLASH_MEMTYPE_PROG)
  {
    FLASH->PUKR = FLASH_RASS_KEY1;
    FLASH->PUKR = FLASH_RASS_KEY2;
  }
  /* Unlock data memory */
  else
  {
    FLASH->DUKR = FLASH_RASS_KEY2; /* Warning: keys are reversed on data memory !!! */
    FLASH->DUKR = FLASH_RASS_KEY1;
  }
}

but nothing works reliably.
It might work sometimes but not always.
You google for the solutions and ends up here.

My encounter

Perhaps, I got more luck than you people because the hardware faults in my application can reveal some info of the failure. I am storing counting value in the EEPROM. My code is not that much different from Mark Stevens', but since I am storing counting value, I immediately notice that for some reasons only even value has been saved to the EEPROM. This behavior also matched with what Mark Stevens reported. However, in my case, unlike Mark Stephens', I can be sure that the failure is not random.

If you want to see what I see or testing out my software on your hardware you can grab the code with build instructions from this branch.

Eureka

Anyone seeing the result would immediately conclude that the order of key is wrong.
Our KEY1 is CPU's KEY2, and our KEY2 is CPU's KEY1.

Our perspective | CPU perspective
----------------+----------------------------------
Send KEY1       | Receive KEY2, Do nothing
Send KEY2       | Receive KEY1, Do nothing
Write           | Do nothing
Lock            | Do nothing
Send KEY1       | Receive KEY2, Unlock EEPROM write
Send KEY2       | Receive KEY1, Do nothing
Write           | Write to EEPROM
Lock            | Restrict EEPROM write
Send KEY1       | Receive KEY2, Do nothing
Send KEY2       | Receive KEY1, Do nothing
Write           | Do nothing
Lock            | Do nothing
Send KEY1       | Receive KEY2, Unlock EEPROM write
Send KEY2       | Receive KEY1, Do nothing
Write           | Write to EEPROM
Lock            | Restrict EEPROM write

This is why I got only even numbers in the EEPROM.
Once concluded I unreverse the key so the MASS keys sequence would effectively be the same for both program memory and data memory.

FLASH->DUKR = FLASH_RASS_KEY2; /* Warning: keys are reversed on data memory !!! */
FLASH->DUKR = FLASH_RASS_KEY1;

become

FLASH->DUKR = FLASH_RASS_KEY1;
FLASH->DUKR = FLASH_RASS_KEY2;

And it works!!!
I can now record both odd and even numbers.

But wait does it end there, did I discovering anything new here?

Unfortunately, I knew right away that I only just rediscovered what ST's engineers encountered years ago. There must be a good reason to make them comment something like this in their code.

FLASH->DUKR = FLASH_RASS_KEY2; /* Warning: keys are reversed on data memory !!! */
FLASH->DUKR = FLASH_RASS_KEY1;

I presume that the reversal of key order is unexpected even inside of ST. They changed and documented their "corrected" version of the key sequence everywhere.
I suspected that the good reason is that ST's engineers observed and saw the same behavior I have seen, and the solution to the problem is to reverse the order of MASS keys.

The real bug

It seems that the real hardware bug here is the fact that the internal state of FLASH_DUKR register did not get reset properly. It can't merely solve by reverse the order of MASS keys. The actual issue lies beyond.

The workaround/mitigation

Luckily, software solutions to mitigate the bug is possible. We can exploit the fact that STM8 CPU allow trying the first byte of MASS keys for EEPROM unlocking.

If the key available on the data bus is incorrect, the application can re-enter two MASS keys to try unprotecting the DATA area

Even if we don't know which bytes the FLASH_DUKR register will consider as the first byte of the unlocking sequence. We can just do

while (!(FLASH->IAPSR & FLASH_IAPSR_DUL)) {
  // the order here does not matter anymore
  FLASH->DUKR = FLASH_RASS_KEY1;
  FLASH->DUKR = FLASH_RASS_KEY2;
}

to crack the code.
There are 2 possible scenarios for the workaround code.
Either

Our perspective | CPU perspective
----------------+----------------------------------
Send KEY1       | Receive KEY1, Do nothing
Send KEY2       | Receive KEY2, Unlock EEPROM write
Write           | Write to EEPROM
Lock            | Restrict EEPROM write

or

Our perspective | CPU perspective
----------------+----------------------------------
Send KEY1       | Receive KEY2, Do nothing
Send KEY2       | Receive KEY1, Do nothing
Send KEY1       | Receive KEY2, Unlock EEPROM write
Send KEY2       | Receive KEY1, Do nothing
Write           | Write to EEPROM
Lock            | Restrict EEPROM write

we will always get the result we want.

You can grab the example workaround code with full build instructions from this branch.

And that's it. I think that is what you came here for.

Further observation

After the CPU execute the workaround code for some time, the key sequence seems to be stable at the reversed order, just as described in the reference manual. I don't know how to reset it back to the ascending order state, but there is no guarantee it will never change again. So I would recommend using the workaround solution even if your microcontrollers are fine with the EEPROM now, it works regardless of the ordering.

About the author (Plug)

Sarun Rattanasiri
A full stack software architect, being able to work with small devices, complex enterprise backend systems, beautiful and robust frontend, and everything in between. Proficient in both business and technical languages. Not the smartest kid in the room, not a fast learner, but one who understands.

In my free time, I like to read, sleep, and do some lite coding. I have a broad knowledge of many things. If you want to discuss just drop me an email at the same address as appeared on git commits in this repository.

Special thanks to people who documented the prior discoveries