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

Improper EXTI reset in HAL_GPIO_DeInit() #1

Closed
jelledevleeschouwer opened this issue Mar 18, 2020 · 5 comments
Closed

Improper EXTI reset in HAL_GPIO_DeInit() #1

jelledevleeschouwer opened this issue Mar 18, 2020 · 5 comments
Assignees
Labels
bug Something isn't working hal HAL-LL driver-related issue or pull-request. spotted before customer Spotted and fixed internally before being pointed out by users but not published yet
Milestone

Comments

@jelledevleeschouwer
Copy link

jelledevleeschouwer commented Mar 18, 2020

Dear @ALABSTM,

TL;DR

The HAL_GPIO_DeInit() contains a bug related to the order in which GPIO configuration and EXTI configuration is reset to its "reset defaults". The issue seems not to be present in other HAL drivers, such as for example in stm32f4xx_hal_driver. In those HAL drivers, disabling the EXTI interrupt comes before resetting the GPIO configuration as it should be.

Scenario

Let's assume you have an active low switch (switch with external pull-up) connected to a GPIO pin and one would like to trigger an EXTI interrupt on the falling edge when closing that switch. But, in the meantime (since number of GPIO pins is limited), that GPIO is shared with another hardware resource. In rest, the GPIO is configured for that latter/second resource. One would typically see the following scenario to switch to input with EXTI interrupt on falling edge:

  1. Reconfigure the GPIO for EXTI with GPIO_MODE_IT_FALLING and all boiler plate code using GPIO_Init().
  2. Wait for the EXTI interrupt to fire or timeout
  3. Restore GPIO configuration to map on that second hardware resource with GPIO_Init().

Problem

In the scenario above, when that hardware resource is for example an output and the output isn't driven, the EXTI interrupt might fire since a '1'-to-'0' transition is observed on the EXTI line and that EXTI interrupt is still enabled.

Cause

The only way to clear EXTI configuration is by using HAL_GPIO_DeInit()

Solution

Use HAL_GPIO_DeInit() to clear EXTI configuration to reset defaults.

Follow-up problem 1

The solution for the problem causes another problem: in HAL_GPIO_DeInit() the I/O mode is reset to analog high impedance before the EXTI is disabled!

GPIOx->MODER |= (GPIO_MODER_MODE0 << (position * 2U));
/* Configure the default Alternate Function in current IO */
GPIOx->AFR[position >> 3U] &= ~((uint32_t)0xFU << ((uint32_t)(position & (uint32_t)0x07U) * 4U)) ;
/* Configure the default value for IO Speed */
GPIOx->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEED0 << (position * 2U));
/* Configure the default value IO Output Type */
GPIOx->OTYPER &= ~(GPIO_OTYPER_OT_0 << position) ;
/* Deactivate the Pull-up oand Pull-down resistor for the current IO */
GPIOx->PUPDR &= ~(GPIO_PUPDR_PUPD0 << (position * 2U));
/*------------------------- EXTI Mode Configuration --------------------*/
/* Clear the External Interrupt or Event for the current IO */
tmp = SYSCFG->EXTICR[position >> 2U];
tmp &= (((uint32_t)0x0FU) << (4U * (position & 0x03U)));
if(tmp == (GPIO_GET_INDEX(GPIOx) << (4U * (position & 0x03U))))
{
tmp = ((uint32_t)0x0FU) << (4U * (position & 0x03U));
SYSCFG->EXTICR[position >> 2U] &= ~tmp;
/* Clear EXTI line configuration */
EXTI->IMR &= ~((uint32_t)iocurrent);

The GPIO analog high impedance configuration disables the internal TTL Schmitt trigger producing a '0' on its ouput (See Figure 26 on page 242 in RM0376). Again, a '1'-to-'0' transition is observed on the EXTI line and that EXTI interrupt is still enabled. The EXTI interrupt will fire again.

Cause

The code block responsible for resetting the I/O mode of the GPIO comes before the code block responsible for resetting the EXTI configuration.

Solution

Swap the two code blocks in HAL_GPIO_DeInit() around.

Follow-up problem 2

There is still another problem in the code block responsible for resetting the EXTI configuration itself: the line multiplexer is reset before the EXTI is masked! This means that somehow the input data on GPIOA is '0', again a '1'-to-'0' transition is observed on the EXTI line and that EXTI interrupt is still enabled. The EXTI interrupt will fire again.

Cause

The code resetting the EXTI line multiplexer:

tmp = ((uint32_t)0x0FU) << (4U * (position & 0x03U));
SYSCFG->EXTICR[position >> 2U] &= ~tmp;

comes before the code resetting the EXTI interrupt masks:
/* Clear EXTI line configuration */
EXTI->IMR &= ~((uint32_t)iocurrent);
EXTI->EMR &= ~((uint32_t)iocurrent);
/* Clear Rising Falling edge configuration */
EXTI->RTSR &= ~((uint32_t)iocurrent);
EXTI->FTSR &= ~((uint32_t)iocurrent);

Solution

Swap the two code blocks around.

What do you think? Could you please take a look at this?

Thanks,
Best regards,

Jelle De Vleeschouwer

jelledevleeschouwer added a commit to jelledevleeschouwer/stm32l0xx_hal_driver that referenced this issue Mar 27, 2020
Scenario:
Firmware configures a GPIO for EXTI line interrupt on falling edge. In
hardware the GPIO is pulled to Vdd by a pull-up resistor. After the
falling edge is detected (single instance), the firmware reconfigures
the GPIO pin for a different purpose.

Problem:
EXTI line interrupt is triggered twice, while in fact only one falling
edge is generated on the I/O pin.

Root cause:
Improper EXTI reset order in HAL_GPIO_DeInit(). After the first time the
EXTI line interrupt is fired, the firmware calls HAL_GPIO_DeInit() to
de-initialize the GPIO pin because the resource is not needed anymore.
Because the way the code is ordered in HAL_GPIO_DeInit(), '1'-to-'0'
transition might be arise in the "Input data register" because the
mode is reset to Analog High Impedance mode. This disable the internal
Schmitt trigger, which continuously produces a '0' on its output.
If the I/O was pulled high by pull-up resistor, a '1'-to-'0' transition
triggers the EXTI line edge detector and fires the IRQ because the EXTI
line configuration registers are not yet reset at this point in
HAL_GPIO_DeInit().

Solution:
Reorder the EXTI reset code in HAL_GPIO_DeInit() to disable the EXIT
line interrupt before switching the value of EXTI line multiplexer and
resetting the mode of the GPIO pin to analog high-impedance mode.

Fixes STMicroelectronics#1.
@ALABSTM ALABSTM self-assigned this Apr 8, 2020
@ALABSTM
Copy link
Collaborator

ALABSTM commented Apr 14, 2020

Dear @jelledevleeschouwer,

First of all, allow me to thank you for this detailed report. The accuracy of your analysis is remarkable.

We are already aware of this issue and have fixed it internally. The fix will be available in a future release. Unfortunately we cannot share a date for the moment. We count on your patience and comprehension.

However, I would like to ask you about what you described in the "Follow-up problem 2" section. Could you systematically reproduce the issue while still having the the line multiplexer reset before the EXTI is masked?

Thank you in advance for your answer and thank you once more for this detailed report.

With regards,

@ALABSTM ALABSTM added bug Something isn't working internal bug tracker Issue confirmed and reported into a ticket in the internal bug tracking system spotted before customer Spotted and fixed internally before being pointed out by users but not published yet labels Apr 14, 2020
@ALABSTM
Copy link
Collaborator

ALABSTM commented Apr 14, 2020

ST Internal Reference: 77661

@ALABSTM ALABSTM moved this from To do to In progress in stm32cube-mcu-hal-dashboard Apr 22, 2020
@ALABSTM
Copy link
Collaborator

ALABSTM commented Jul 20, 2020

Hi @jelledevleeschouwer,

Any reply from your side about what you described in the "Follow-up problem 2" section?

With regards,

@jelledevleeschouwer
Copy link
Author

jelledevleeschouwer commented Jul 23, 2020

Hi @ALABSTM,

Yes, at the time I could reproduce the problem described in "Follow-up problem 2", but I don't have the time to try and reproduce the issue right now.

But the idea is simple, no? Imagine you have a GPIO (say, PB6) with external pull-up configured as digital input with EXTI interrupt on a falling edge. In rest, on the PB6 GPIO a high level '1' is present. But let's imagine you call HAL_GPIO_DeInit() now with a fix for the first issue as follows:

            /*------------------------- EXTI Mode Configuration --------------------*/
            /* Clear the External Interrupt or Event for the current IO */

            tmp = SYSCFG->EXTICR[position >> 2U];
            tmp &= (((uint32_t)0x0FU) << (4U * (position & 0x03U)));
            if(tmp == (GPIO_GET_INDEX(GPIOx) << (4U * (position & 0x03U))))
            {
                tmp = ((uint32_t)0x0FU) << (4U * (position & 0x03U));
                SYSCFG->EXTICR[position >> 2U] &= ~tmp;

                /* Clear EXTI line configuration */
                EXTI->IMR &= ~((uint32_t)iocurrent);
                EXTI->EMR &= ~((uint32_t)iocurrent);

                /* Clear Rising Falling edge configuration */
                EXTI->RTSR &= ~((uint32_t)iocurrent);
                EXTI->FTSR &= ~((uint32_t)iocurrent);
            }

            /*------------------------- GPIO Mode Configuration --------------------*/
            /* Configure IO Direction in Input Floting Mode */
            GPIOx->MODER |= (GPIO_MODER_MODE0 << (position * 2U));
            
            /* ... */

The code addresses SYSCFG_EXTICR2 in case of GPIO_PIN_6. tmp will be be 0x0100 when entering the if-statement. (GPIO_GET_INDEX(GPIOx) << (4U * (position & 0x03U))) will also yield 0x0100 which makes the code enter the if-statement. In the if-statement bits EXTI6[3:0] are cleared in SYSCFG_EXTICR2, which makes the PA6 GPIO to be selected as the source input for the EXTI6 external interrupt. At this point, the EXTI line is still not masked!

If at this point, the PA6 GPIO was now configured as Digital Input and a low value '0' is produced on that GPIO, a '1'-to-'0' (falling edge) will be detected by the EXTI "edge detect cricuitry" and thus will set the Pending Interrupt flag on line 6 (PIF6) in the EXTI_PR register.

This is the behavior that I observed. Could that be plausible, or am I missing something?

Thanks,
Best regards,
Jelle

@RKOUSTM RKOUSTM added the hal HAL-LL driver-related issue or pull-request. label Feb 5, 2021
@ALABSTM
Copy link
Collaborator

ALABSTM commented Sep 14, 2021

Hi @jelledevleeschouwer,

I hope you are fine. I noticed you attached p-r #2 to this issue. The fix you proposed is already covered by the one published in the frame of v1.10.3. I assume the p-r covers both problems 1 and 2 you described above.

Please allow me then to close this issue. Thank you for your comprehension and thank you again for your detailed and well structured report.

With regards,

@ALABSTM ALABSTM closed this as completed Sep 14, 2021
stm32cube-mcu-hal-dashboard automation moved this from In progress to Done Sep 14, 2021
@ALABSTM ALABSTM added this to the 1.10.3 milestone Sep 14, 2021
@ALABSTM ALABSTM removed the internal bug tracker Issue confirmed and reported into a ticket in the internal bug tracking system label Sep 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working hal HAL-LL driver-related issue or pull-request. spotted before customer Spotted and fixed internally before being pointed out by users but not published yet
Development

Successfully merging a pull request may close this issue.

3 participants