| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- // SPDX-License-Identifier: BSD-2-Clause
- // Copyright (c) 2023 KBEmbedded
- #include <furi.h>
- #include <furi_hal.h>
- #include <stm32wbxx_ll_exti.h>
- #include <stm32wbxx_ll_system.h>
- #include <stdint.h>
- #include "gblink.h"
- const struct gblink_pins common_pinouts[PINOUT_COUNT] = {
- /* Original */
- {
- &gpio_ext_pc3,
- &gpio_ext_pb3,
- &gpio_ext_pb2,
- &gpio_ext_pa4,
- },
- /* MALVEKE EXT1 */
- {
- &gpio_ext_pa6,
- &gpio_ext_pa7,
- &gpio_ext_pb3,
- &gpio_ext_pa4,
- },
- };
- struct gblink {
- const GpioPin *serin;
- const GpioPin *serout;
- const GpioPin *clk;
- const GpioPin *sd;
- uint8_t in;
- uint8_t out;
- uint8_t out_buf;
- bool out_buf_valid;
- uint8_t shift;
- uint8_t nobyte;
- gblink_clk_source source;
- gblink_mode mode;
- gblink_speed speed;
- uint32_t time;
- uint32_t bitclk_timeout_us;
- /* Clocks idle between bytes is nominally 430 us long for burst data,
- * 15 ms for idle polling (e.g. waiting for menu selection), some oddball
- * 2 ms gaps that appears between one 0xFE byte from the Game Boy every trade;
- * clock period is nominally 122 us.
- * Therefore, if we haven't seen a clock in 500 us, reset our bit counter.
- * Note that, this should never actually be a concern, but it is an additional
- * safeguard against desyncing.
- */
- void (*callback)(void* cb_context, uint8_t in);
- void *cb_context;
- uint32_t* ivt_mirror;
- uint32_t ivt_mirror_offs;
- bool exti3_rise_enable;
- bool exti3_fall_enable;
- bool exti3_event_enable;
- };
- static void gblink_shift_in(struct gblink *gblink)
- {
- const uint32_t time_ticks = furi_hal_cortex_instructions_per_microsecond() * gblink->bitclk_timeout_us;
- /* If we exceeded the bit clock timeout, reset all counters */
- if ((DWT->CYCCNT - gblink->time) > time_ticks) {
- gblink->in = 0;
- gblink->shift = 0;
- }
- gblink->time = DWT->CYCCNT;
- gblink->in <<= 1;
- gblink->in |= furi_hal_gpio_read(gblink->serin);
- gblink->shift++;
- /* If 8 bits transfered, reset shift counter, call registered
- * callback, re-set nobyte in output buffer.
- */
- if (gblink->shift == 8) {
- gblink->shift = 0;
- /* Set up next out byte before calling the callback.
- * This is in case the callback itself sets a new out
- * byte which it will in most cases. It is up to the
- * main application at this time to ensure that
- * gblink_transfer() isn't called multiple times before
- * a byte has a chance to be sent out.
- */
- if (gblink->out_buf_valid) {
- gblink->out = gblink->out_buf;
- gblink->out_buf_valid = false;
- } else {
- gblink->out = gblink->nobyte;
- }
- gblink->callback(gblink->cb_context, gblink->in);
- }
- }
- static void gblink_shift_out(struct gblink *gblink)
- {
- furi_hal_gpio_write(gblink->serout, !!(gblink->out & 0x80));
- gblink->out <<= 1;
- }
- static void gblink_clk_isr(void *context)
- {
- furi_assert(context);
- struct gblink *gblink = context;
- if (furi_hal_gpio_read(gblink->clk)) {
- /* Posedge Shift in data */
- gblink_shift_in(gblink);
- } else {
- /* Negedge shift out data */
- gblink_shift_out(gblink);
- }
- }
- /* NOTE WELL! This function is absurdly hacky and a stupid workaround to a
- * stupid issue that doesn't really have any other solution in the current
- * Flipper/FURI API. I'm over-commenting this so we know exactly what is going
- * on if we ever have to re-visit this mess.
- *
- * This block of text below describes the overall idea, more specific comments
- * in the function body.
- *
- * TODO: make this more generic for any other GPIOs that might conflict with
- * exti interrupts. PA6, PB3, PC3, PB2? (NFC), PA13, PB6
- * NOTE: This is only set up at the moment for PB3, hardcoded
- *
- * There are multiple problems that this workaround is handling. EXTI interrupts
- * are shared among multiple pins. The FURI core maintains per-pin ISRs in a
- * private struct that has no way to read, save, or otherwise be able to put
- * back the ISR that would service a conflicting EXTI. e.g. PB3 and PH3
- * (the OK button) both share EXTI3. Setting an interrupt on PB3 will clobber
- * the FURI ISR callback/context pair as well as change EXTI3 to use PB3 as
- * the interrupt source.
- *
- * To make an interrupt work correctly on PB3 and not break the OK button
- * we need a way to set an interrupt for PB3 in a way that doesn't clobber the
- * private FURI GPIO ISR handles and can let the interrupt for the OK button
- * work again when we're done.
- *
- * The general concept of this workaround is to modify the IVT to create our
- * own handler for EXTI3 interrupts. Doing this leaves the aforementioned private
- * GPIO struct unmodified and disables the OK button from triggering an interrupt.
- * The IVT is normally located at the lowest addresses of flash (which is located
- * at 0x08000000 and mapped at runtime to 0x00000000); this means the IVT cannot
- * be changed at runtime.
- *
- * To make this work, we use the Vector Table Offset Register (VTOR) in the
- * System Control Block (SCB). The VTOR allows for changing the location of the
- * IVT. We copy the IVT to a location in memory, and then do a dance to safely
- * set up the GPIO interrupt to PB3, and swap in our IVT with the modified EXTI3
- * handler.
- *
- * When undoing this, the process is not quite in reverse as we have to put back
- * specific interrupt settings that we very likely would have clobbered but have
- * the ability to save beforehand.
- *
- * Wrapping the steps in disabling the EXTI3 interrupt is probably not needed,
- * but is a precaution since we are changing the interrupt sources in weird ways.
- */
- /* Used to map our callback context in a way the handler can access */
- static void *exti3_cb_context;
- static void gblink_exti3_IRQHandler(void) {
- if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_3)) {
- gblink_clk_isr(exti3_cb_context);
- LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_3);
- }
- }
- static void gblink_gross_exti_workaround(struct gblink *gblink)
- {
- /* This process makes a number of assumptions, including that the IVT
- * is located at 0x00000000, that the lowest flash page is mapped to
- * that base address, and that the VTOR points to 0x00000000.
- * There are runtime protections in place to prevent reading from the
- * first 1 MB of addresses. So we have to always assume that the lowest
- * page of flash is mapped to 0x00000000 and read the IVT from the that
- * page in flash directly.
- * The only check we can really do here is ensuring VTOR is 0 and that
- * Main memory is mapped to 0x00000000. If either of those are not true,
- * then we can't continue.
- */
- furi_check(SCB->VTOR == 0x0);
- furi_check(LL_SYSCFG_GetRemapMemory() == LL_SYSCFG_REMAP_FLASH);
- /* Create a mirror of the existing IVT from CPU 1
- * The IVT on this platform has 79 entries; 63 maskable, 10 non-maskable,
- * 6 reserved. The maskable interrupts start at offset 16.
- * CMSIS documentation says that the boundary for IVT must be aligned to
- * the number of interrupts, rounded up to the nearest power of two, and
- * then multiplied by the word width of the CPU. 79 rounds up to 128
- * with a word width of 4, this is 512/0x200 bytes.
- * As there is no good way with FreeRTOS to request an alloc at an
- * aligned boundary, allocate the amount of data we need, plus 0x200
- * bytes, to guarantee that we can put the table in a location that is
- * properly aligned. Once we find a suitable base address, this offset
- * is saved for later.
- */
- gblink->ivt_mirror = malloc((79 * sizeof(uint32_t)) + 0x200);
- gblink->ivt_mirror_offs = (uint32_t)gblink->ivt_mirror;
- while (gblink->ivt_mirror_offs & 0x1FF)
- gblink->ivt_mirror_offs++;
- /* 0x08000000 is used instead of 0x00000000 because everything complains
- * using a NULL pointer.
- */
- memcpy((uint32_t *)gblink->ivt_mirror_offs, ((uint32_t *)0x08000000), 79 * sizeof(uint32_t));
- /* Point our IVT's EXTI3 interrupt to our desired interrupt handler.
- * Also copy the gblink struct to the global var that the interrupt
- * handler will use to make further calls.
- */
- ((uint32_t *)gblink->ivt_mirror_offs)[25] = (uint32_t)gblink_exti3_IRQHandler; // 16 NMI + offset of 9 for EXTI3
- exti3_cb_context = gblink;
- /* Disable the EXTI3 interrupt. This lets us do bad things without
- * fear of an IRQ hitting in the middle.
- */
- LL_EXTI_DisableIT_0_31(LL_EXTI_LINE_3);
- /* Save the existing rise/fall trigger settings. In theory, these should
- * really never change through the life of the flipper OS. But for safety
- * we always save them rather than just blindly restoring the same settings
- * back when we undo this later.
- */
- gblink->exti3_rise_enable = LL_EXTI_IsEnabledRisingTrig_0_31(LL_EXTI_LINE_3);
- gblink->exti3_fall_enable = LL_EXTI_IsEnabledFallingTrig_0_31(LL_EXTI_LINE_3);
- gblink->exti3_event_enable = LL_EXTI_IsEnabledEvent_0_31(LL_EXTI_LINE_3);
- /* Now, set up our desired pin settings. This will only clobber exti3
- * settings and will not affect the actual interrupt vector address.
- * Settings include the rising/falling/event triggers which we just
- * saved.
- */
- furi_hal_gpio_init(gblink->clk, GpioModeInterruptRiseFall, GpioPullUp, GpioSpeedVeryHigh);
- /* Update the NVIC table to point at our desired table.
- * Out of safety, stop the world around changing the VTOR reg.
- */
- FURI_CRITICAL_ENTER();
- SCB->VTOR = gblink->ivt_mirror_offs;
- FURI_CRITICAL_EXIT();
- /* Last, enable the interrupts and hope everything works. */
- LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_3);
- }
- static void gblink_gross_exti_workaround_undo(struct gblink *gblink)
- {
- /* First, disable the EXTI3 interrupt. This lets us do bad things without
- * fear of an IRQ hitting in the middle.
- */
- LL_EXTI_DisableIT_0_31(LL_EXTI_LINE_3);
- /* Set the correct input source, PH3/OK button, to EXTI3. It is important
- * to do this before calling furi_hal_gpio_init() on PB3. When that func
- * is called with no interrupt settings enabled, if the EXTI source
- * matches the pin, and the interrupt is enabled, interrupts will be
- * disabled. By manually setting the EXTI3 source here, it no longer
- * matches the PB3 pin, and our changing of IO settings on our GPIO pin
- * to no longer have interrupts will not affect the shared IRQ.
- */
- LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTH, LL_SYSCFG_EXTI_LINE3);
- /* Set the correct rise/fall/event settings back */
- if (gblink->exti3_rise_enable)
- LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINE_3);
- else
- LL_EXTI_DisableRisingTrig_0_31(LL_EXTI_LINE_3);
- if (gblink->exti3_fall_enable)
- LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINE_3);
- else
- LL_EXTI_DisableFallingTrig_0_31(LL_EXTI_LINE_3);
- if (gblink->exti3_event_enable)
- LL_EXTI_EnableEvent_0_31(LL_EXTI_LINE_3);
- else
- LL_EXTI_DisableEvent_0_31(LL_EXTI_LINE_3);
- /* "Release" the GPIO by putting it back in a known idle state. */
- furi_hal_gpio_init_simple(gblink->clk, GpioModeAnalog);
- /* Set the IVT back to the normal, in-flash table. Stopping the world
- * while we do so.
- * NOTE: This just assumes the VTOR is always at 0x0 by default, if this
- * ever changes in the Flipper OS, then that will be a problem.
- */
- FURI_CRITICAL_ENTER();
- SCB->VTOR = 0x0;
- FURI_CRITICAL_EXIT();
- /* Re-enable the interrupt, OK button should work again. */
- LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_3);
- /* Free the alloc()ed mirror space */
- free(gblink->ivt_mirror);
- }
- void gblink_clk_source_set(void *handle, int source)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- gblink->source = source;
- gblink->shift = 0;
- }
- void gblink_speed_set(void *handle, gblink_speed speed)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- gblink->speed = speed;
- }
- /* default is set to 500 us */
- void gblink_timeout_set(void *handle, uint32_t us)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- gblink->bitclk_timeout_us = us;
- }
- void gblink_transfer(void *handle, uint8_t val)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- /* This checks the value of gblink->shift which can change in the ISR.
- * Because of that, disable interrupts when checking gblink->shift and
- * setting gblink->out_buf_valid
- * If shift is 0, we're between bytes and can safely set the out byte.
- * If shift is nonzero, a byte is currently being transmitted. Set the
- * out_buf and set out_buf_valid. When the ISR is finished writing the
- * next byte it will check out_buf_valid and copy in out_buf.
- *
- * The correct/smart way of doing this would be a mutex rather than
- * stopping the world.
- *
- * Realistically, this should only ever be called from the transfer
- * complete callback. There are few situations outside of that which
- * would make sense.
- *
- * Note that, this currently has no checks for if there is data already
- * pending to be transmitted. Calling this back to back can cause data
- * loss!
- */
- FURI_CRITICAL_ENTER();
- if (gblink->shift == 0) {
- gblink->out = val;
- gblink->out_buf_valid = false;
- } else {
- gblink->out_buf = val;
- gblink->out_buf_valid = true;
- }
- FURI_CRITICAL_EXIT();
- }
- void gblink_nobyte_set(void *handle, uint8_t val)
- {
- struct gblink *gblink = handle;
- gblink->nobyte = val;
- }
- void gblink_int_enable(void *handle)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- furi_hal_gpio_enable_int_callback(gblink->clk);
- }
- void gblink_int_disable(void *handle)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- furi_hal_gpio_disable_int_callback(gblink->clk);
- }
- void *gblink_alloc(struct gblink_def *gblink_def)
- {
- struct gblink *gblink;
- /* Allocate and zero struct */
- gblink = malloc(sizeof(struct gblink));
- /* Set struct values from function args */
- gblink->serin = gblink_def->pins->serin;
- gblink->serout = gblink_def->pins->serout;
- gblink->clk = gblink_def->pins->clk;
- gblink->sd = gblink_def->pins->sd;
- gblink->source = gblink_def->source;
- gblink->speed = GBLINK_SPD_8192HZ;
- /* Set up timeout variables */
- gblink->bitclk_timeout_us = 500;
- gblink->time = DWT->CYCCNT;
- /* Set up secondary callback */
- gblink->callback = gblink_def->callback;
- gblink->cb_context = gblink_def->cb_context;
- /* Set up pins */
- /* TODO: Set up a list of pins that are not safe to use with interrupts.
- * I do believe the main FURI GPIO struct has this data baked in so that
- * could be used. For now though, we're only checking for the MALVEKE
- * pinout which uses a clk pin that has its IRQ shared with the Okay
- * button.
- * See the work done in pokemon trade tool custom pinout selection for
- * an idea of how to check all that.
- */
- /* TODO: Currently assumes external clock source only */
- /* XXX: This might actually be open-drain on real GB hardware */
- furi_hal_gpio_write(gblink->serout, false);
- furi_hal_gpio_init(gblink->serout, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
- furi_hal_gpio_write(gblink->serin, false);
- furi_hal_gpio_init(gblink->serin, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
- /* Set up interrupt on clock */
- if (gblink->clk == &gpio_ext_pb3) {
- /* The clock pin is on a pin that is not safe to set an interrupt
- * on, so we do a gross workaround to get an interrupt enabled
- * on that pin in a way that can be undone safely later with
- * no impact to the shared IRQ.
- */
- gblink_gross_exti_workaround(gblink);
- } else {
- furi_hal_gpio_init(gblink->clk, GpioModeInterruptRiseFall, GpioPullUp, GpioSpeedVeryHigh);
- /* This may not be needed after NFC refactor */
- furi_hal_gpio_remove_int_callback(gblink->clk);
- furi_hal_gpio_add_int_callback(gblink->clk, gblink_clk_isr, gblink);
- }
- return gblink;
- }
- void gblink_free(void *handle)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- if (gblink->clk == &gpio_ext_pb3) {
- /* This handles switching the IVT back and putting the EXTI
- * regs and pin regs in a valid state for normal use.
- */
- gblink_gross_exti_workaround_undo(gblink);
- } else {
- /* Remove interrupt, set IO to sane state */
- furi_hal_gpio_remove_int_callback(gblink->clk);
- }
- furi_hal_gpio_init_simple(gblink->serin, GpioModeAnalog);
- furi_hal_gpio_init_simple(gblink->serout, GpioModeAnalog);
- furi_hal_gpio_init_simple(gblink->clk, GpioModeAnalog);
- free(gblink);
- }
|