exti_workaround.c 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // SPDX-License-Identifier: BSD-2-Clause
  2. // Copyright (c) 2023 KBEmbedded
  3. #include <furi.h>
  4. #include <furi_hal.h>
  5. #include <stm32wbxx_ll_exti.h>
  6. #include <stm32wbxx_ll_system.h>
  7. #include <stdint.h>
  8. struct exti_workaround {
  9. uint32_t* ivt_mirror;
  10. uint32_t ivt_mirror_offs;
  11. bool exti3_rise_enable;
  12. bool exti3_fall_enable;
  13. bool exti3_event_enable;
  14. const GpioPin *clk;
  15. };
  16. /* NOTE WELL! This function is absurdly hacky and a stupid workaround to a
  17. * stupid issue that doesn't really have any other solution in the current
  18. * Flipper/FURI API. I'm over-commenting this so we know exactly what is going
  19. * on if we ever have to re-visit this mess.
  20. *
  21. * This block of text below describes the overall idea, more specific comments
  22. * in the function body.
  23. *
  24. * TODO: make this more generic for any other GPIOs that might conflict with
  25. * exti interrupts. PA6, PB3, PC3, PB2? (NFC), PA13, PB6
  26. * NOTE: This is only set up at the moment for PB3, hardcoded
  27. *
  28. * There are multiple problems that this workaround is handling. EXTI interrupts
  29. * are shared among multiple pins. The FURI core maintains per-pin ISRs in a
  30. * private struct that has no way to read, save, or otherwise be able to put
  31. * back the ISR that would service a conflicting EXTI. e.g. PB3 and PH3
  32. * (the OK button) both share EXTI3. Setting an interrupt on PB3 will clobber
  33. * the FURI ISR callback/context pair as well as change EXTI3 to use PB3 as
  34. * the interrupt source.
  35. *
  36. * To make an interrupt work correctly on PB3 and not break the OK button
  37. * we need a way to set an interrupt for PB3 in a way that doesn't clobber the
  38. * private FURI GPIO ISR handles and can let the interrupt for the OK button
  39. * work again when we're done.
  40. *
  41. * The general concept of this workaround is to modify the IVT to create our
  42. * own handler for EXTI3 interrupts. Doing this leaves the aforementioned private
  43. * GPIO struct unmodified and disables the OK button from triggering an interrupt.
  44. * The IVT is normally located at the lowest addresses of flash (which is located
  45. * at 0x08000000 and mapped at runtime to 0x00000000); this means the IVT cannot
  46. * be changed at runtime.
  47. *
  48. * To make this work, we use the Vector Table Offset Register (VTOR) in the
  49. * System Control Block (SCB). The VTOR allows for changing the location of the
  50. * IVT. We copy the IVT to a location in memory, and then do a dance to safely
  51. * set up the GPIO interrupt to PB3, and swap in our IVT with the modified EXTI3
  52. * handler.
  53. *
  54. * When undoing this, the process is not quite in reverse as we have to put back
  55. * specific interrupt settings that we very likely would have clobbered but have
  56. * the ability to save beforehand.
  57. *
  58. * Wrapping the steps in disabling the EXTI3 interrupt is probably not needed,
  59. * but is a precaution since we are changing the interrupt sources in weird ways.
  60. */
  61. /* Used to map our callback context in a way the handler can access */
  62. static void *exti3_cb_context;
  63. static void (*callback)(void *context);
  64. static void gblink_exti3_IRQHandler(void) {
  65. if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_3)) {
  66. callback(exti3_cb_context);
  67. LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_3);
  68. }
  69. }
  70. void *exti_workaround(const GpioPin *clk, void (*isr_callback)(void *context), void *context)
  71. {
  72. struct exti_workaround *work = NULL;
  73. /* This process makes a number of assumptions, including that the IVT
  74. * is located at 0x00000000, that the lowest flash page is mapped to
  75. * that base address, and that the VTOR points to 0x00000000.
  76. * There are runtime protections in place to prevent reading from the
  77. * first 1 MB of addresses. So we have to always assume that the lowest
  78. * page of flash is mapped to 0x00000000 and read the IVT from the that
  79. * page in flash directly.
  80. * The only check we can really do here is ensuring VTOR is 0 and that
  81. * Main memory is mapped to 0x00000000. If either of those are not true,
  82. * then we can't continue.
  83. */
  84. furi_check(SCB->VTOR == 0x0);
  85. furi_check(LL_SYSCFG_GetRemapMemory() == LL_SYSCFG_REMAP_FLASH);
  86. /* Create a mirror of the existing IVT from CPU 1
  87. * The IVT on this platform has 79 entries; 63 maskable, 10 non-maskable,
  88. * 6 reserved. The maskable interrupts start at offset 16.
  89. * CMSIS documentation says that the boundary for IVT must be aligned to
  90. * the number of interrupts, rounded up to the nearest power of two, and
  91. * then multiplied by the word width of the CPU. 79 rounds up to 128
  92. * with a word width of 4, this is 512/0x200 bytes.
  93. * As there is no good way with FreeRTOS to request an alloc at an
  94. * aligned boundary, allocate the amount of data we need, plus 0x200
  95. * bytes, to guarantee that we can put the table in a location that is
  96. * properly aligned. Once we find a suitable base address, this offset
  97. * is saved for later.
  98. */
  99. work = malloc(sizeof(struct exti_workaround));
  100. work->ivt_mirror = malloc((79 * sizeof(uint32_t)) + 0x200);
  101. work->ivt_mirror_offs = (uint32_t)work->ivt_mirror;
  102. while (work->ivt_mirror_offs & 0x1FF)
  103. work->ivt_mirror_offs++;
  104. /* 0x08000000 is used instead of 0x00000000 because everything complains
  105. * using a NULL pointer.
  106. */
  107. memcpy((uint32_t *)work->ivt_mirror_offs, ((uint32_t *)0x08000000), 79 * sizeof(uint32_t));
  108. /* Point our IVT's EXTI3 interrupt to our desired interrupt handler.
  109. * Also copy the gblink struct to the global var that the interrupt
  110. * handler will use to make further calls.
  111. */
  112. ((uint32_t *)work->ivt_mirror_offs)[25] = (uint32_t)gblink_exti3_IRQHandler; // 16 NMI + offset of 9 for EXTI3
  113. callback = isr_callback;
  114. exti3_cb_context = context;
  115. /* Disable the EXTI3 interrupt. This lets us do bad things without
  116. * fear of an IRQ hitting in the middle.
  117. */
  118. LL_EXTI_DisableIT_0_31(LL_EXTI_LINE_3);
  119. /* Save the existing rise/fall trigger settings. In theory, these should
  120. * really never change through the life of the flipper OS. But for safety
  121. * we always save them rather than just blindly restoring the same settings
  122. * back when we undo this later.
  123. */
  124. work->exti3_rise_enable = LL_EXTI_IsEnabledRisingTrig_0_31(LL_EXTI_LINE_3);
  125. work->exti3_fall_enable = LL_EXTI_IsEnabledFallingTrig_0_31(LL_EXTI_LINE_3);
  126. work->exti3_event_enable = LL_EXTI_IsEnabledEvent_0_31(LL_EXTI_LINE_3);
  127. work->clk = clk;
  128. /* Now, set up our desired pin settings. This will only clobber exti3
  129. * settings and will not affect the actual interrupt vector address.
  130. * Settings include the rising/falling/event triggers which we just
  131. * saved.
  132. */
  133. furi_hal_gpio_init(work->clk, GpioModeInterruptRiseFall, GpioPullUp, GpioSpeedVeryHigh);
  134. /* Update the NVIC table to point at our desired table.
  135. * Out of safety, stop the world around changing the VTOR reg.
  136. */
  137. FURI_CRITICAL_ENTER();
  138. SCB->VTOR = work->ivt_mirror_offs;
  139. FURI_CRITICAL_EXIT();
  140. /* Last, enable the interrupts and hope everything works. */
  141. LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_3);
  142. return work;
  143. }
  144. void exti_workaround_undo(void *handle)
  145. {
  146. struct exti_workaround *work = handle;
  147. /* First, disable the EXTI3 interrupt. This lets us do bad things without
  148. * fear of an IRQ hitting in the middle.
  149. */
  150. LL_EXTI_DisableIT_0_31(LL_EXTI_LINE_3);
  151. /* Set the correct input source, PH3/OK button, to EXTI3. It is important
  152. * to do this before calling furi_hal_gpio_init() on PB3. When that func
  153. * is called with no interrupt settings enabled, if the EXTI source
  154. * matches the pin, and the interrupt is enabled, interrupts will be
  155. * disabled. By manually setting the EXTI3 source here, it no longer
  156. * matches the PB3 pin, and our changing of IO settings on our GPIO pin
  157. * to no longer have interrupts will not affect the shared IRQ.
  158. */
  159. LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTH, LL_SYSCFG_EXTI_LINE3);
  160. /* Set the correct rise/fall/event settings back */
  161. if (work->exti3_rise_enable)
  162. LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINE_3);
  163. else
  164. LL_EXTI_DisableRisingTrig_0_31(LL_EXTI_LINE_3);
  165. if (work->exti3_fall_enable)
  166. LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINE_3);
  167. else
  168. LL_EXTI_DisableFallingTrig_0_31(LL_EXTI_LINE_3);
  169. if (work->exti3_event_enable)
  170. LL_EXTI_EnableEvent_0_31(LL_EXTI_LINE_3);
  171. else
  172. LL_EXTI_DisableEvent_0_31(LL_EXTI_LINE_3);
  173. /* "Release" the GPIO by putting it back in a known idle state. */
  174. furi_hal_gpio_init_simple(work->clk, GpioModeAnalog);
  175. /* Set the IVT back to the normal, in-flash table. Stopping the world
  176. * while we do so.
  177. * NOTE: This just assumes the VTOR is always at 0x0 by default, if this
  178. * ever changes in the Flipper OS, then that will be a problem.
  179. */
  180. FURI_CRITICAL_ENTER();
  181. SCB->VTOR = 0x0;
  182. FURI_CRITICAL_EXIT();
  183. /* Re-enable the interrupt, OK button should work again. */
  184. LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_3);
  185. /* Free the alloc()ed mirror space */
  186. free(work->ivt_mirror);
  187. free(work);
  188. }