Deadlock of powerfail sequence during write to flash page
Asked Answered
M

1

6

I'm currently working on an embedded project using an ARM Cortex M3 microcontroller with FreeRTOS as system OS. The code was written by a former colleague and sadly the project has some weird bugs which I have to find and fix as soon as possible.

Short description: The device is integrated into vehicles and sends some "special" data using an integrated modem to a remote server.

The main problem: Since the device is integrated into a vehicle, the power supply of the device can be lost at any time. Therefore the device stores some parts of the "special" data to two reserved flash pages. This code module is laid out as an eeprom emulation on two flash pages(for wear leveling and data transfer from one flash page to another). The eeprom emulation works with so called "virtual addresses", where you can write data blocks of any size to the currently active/valid flash page and read it back by using those virtual addresses. The former colleague implemented the eeprom emulation as multitasking module, where you can read/write to the flash pages from every task in the application. At first sight everything seems fine.

But my project manager told me, that the device always loses some of the "special" data at moments, where the power supply level in the vehicle goes down to some volts and the device tries to save the data to flash. Normally the power supply is about 10-18 volts, but if it goes down to under 7 volts, the device receives an interrupt called powerwarn and it triggers a task called powerfail task. The powerfail task has the highest priority of all tasks and executes some callbacks where e.g. the modem is turned off and also where the "special" data is stored in the flash page. I tried to understand the code and debugged for days/weeks and now I'm quite sure that I found the problem:

Within those callbacks which the powerfail task executes (called powerfail callbacks), there are RTOS calls, where other tasks get suspended. But unfortunately those supended task could also have a unfinished EEPROM_WriteBlock() call just before the powerwarn interrupt is received. Therefore the powerfail task executes the callbacks and in one of the callbacks there is a EE_WriteBlock() call where the task can't take the mutex in EE_WriteBlock() since another task (which was suspended) has taken it already --> Deadlock!

This is the routine to write data to flash:

uint16_t
EE_WriteBlock (EE_TypeDef *EE, uint16_t VirtAddress, const void *Data, uint16_t Size)
{
     .
     .
     xSemaphoreTakeRecursive(EE->rw_mutex, portMAX_DELAY);
     /* Write the variable virtual address and value in the EEPROM */
     .
     .
     .
     xSemaphoreGiveRecursive(EE->rw_mutex);
     return Status;
}

This is the RTOS specific code when 'xSemaphoreTakeRecursive()' is called:

portBASE_TYPE xQueueTakeMutexRecursive( xQueueHandle pxMutex, portTickType xBlockTime )
{
    portBASE_TYPE xReturn;

    /* Comments regarding mutual exclusion as per those within
       xQueueGiveMutexRecursive(). */
    traceTAKE_MUTEX_RECURSIVE( pxMutex );

    if( pxMutex->pxMutexHolder == xTaskGetCurrentTaskHandle() )
    {
        ( pxMutex->uxRecursiveCallCount )++;
        xReturn = pdPASS;
    }
    else
    {
        xReturn = xQueueGenericReceive( pxMutex, NULL, xBlockTime, pdFALSE );

        /* pdPASS will only be returned if we successfully obtained the mutex,
           we may have blocked to reach here. */
        if( xReturn == pdPASS )
        {
            ( pxMutex->uxRecursiveCallCount )++;
        }
        else
        {
            traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
        }
    }

    return xReturn;
}

My project manager is happy that I've found the bug but he also forces me to create a fix as quickly as possible, but what I really want is a rewrite of the code. Maybe one of you might think, just avoid the suspension of the other tasks and you are done, but that is not a possible solution, since this could trigger another bug. Does anybody have a quick solution/idea how I could fix this deadlock problem? Maybe I could use xTaskGetCurrentTaskHandle() in EE_WriteBlock() to determine who has the ownership of the mutex and then give it if the task is not running anymore.

Thx

Marlette answered 24/4, 2014 at 9:36 Comment(0)
C
6

Writing flash, on many systems, requires interrupts to be disabled for the duration of the write so I'm not sure how powerFail can be made running while a write is in progress, but anyway:

Don't control access to the reserved flash pages directly with a mutex - use a blocking producer-consumer queue instead.

Delegate all those writes to one 'flashWriter' thread by queueing requests to it. If the threads requesting the writes require synchronous access, include an event or semaphore in the request struct that the requesting thread waits on after pushing its request. The flashWriter can signal it when done, (or after loading the struct with an error indication:).

There are variations on a theme - if all the write requesting threads need only synchronous access, maybe they can keep their own static request struct with their own semaphore and just queue up a pointer to it.

Use a producer-consumer queue class that allows a high-priority push at the head of the queue and, when powerfail runs, push a 'stopWriting' request at the front of the queue. The flashWriter will then complete any write operation in progress, pop the stopWriting request and so be instructed to suspend itself, (or you could use a 'stop' volatile boolean that the flashWriter checks every time before attempting to pop the queue).

That should prevent deadlock by removing the hard mutex lock from the flash write requests pushed in the other threads. It won't matter if other threads continue to queue up write requests - they will never be executed.

Edit: I've just had two more coffees and, thinking about this, the 'flashWriter' thread could easily become the 'FlashWriterAndPowerFail' thread:

You could arrange for your producer-consumer queue to return a pop() result of null if a volatile 'stop' boolean is set, no matter whether there were entries on the queue or no. In the 'FWAPF' thread, do a null-check after every pop() return and do the powerFail actions upon null or flashWrite actions if not.

When the powerFail interrupt occurs, set the stop bool and signal the 'count' semaphore in the queue to ensure that the FWAPF thread is made running if it's currently blocked on the queue.

That way, you don't need a separate 'powerFail' thread and stack - one thread can do the flashWrite and powerFail while still ensuring that there are no mutex deadlocks.

Compass answered 24/4, 2014 at 10:11 Comment(3)
+1 That's exactly what I would propose. Writing should be done in a single interrupt, all other levels should merely queue requests. If you also allow them to be prioritized, then an "important" request can be executed before other ones.Camilacamile
Sorry, I forgot to mention: When I receive the powerwarn interrupt the powerfail tasks starts executing the callbacks, but the interrupt/interrupts are disabled.Marlette
@Marlette - yeah, I guessed:) My first thought was 'don't suspend the other tasks so that they release the mutex', but that may well not work with interrupts disabled:( Then I thought of the better idea..Compass

© 2022 - 2024 — McMap. All rights reserved.